前言
在进行 MySQL 大版本升级时,我们往往关注语法兼容性、参数变更或性能优化,却容易忽略一个隐藏在底层的“隐形杀手”——排序规则(Collation)。
最近在一次从 MySQL 5.7 到 8.0 的升级迁移中,我们遭遇了一个看似“诡异”的数据问题:原本在 5.7 上能正常插入的两条“看似不同”的数据,在 8.0 上却触发了唯一索引冲突。
本文将复现这一场景,深入分析原因,并提供可落地的解决方案。
一、问题复现:两条数据,为何在 8.0 中“撞车”?
我们构建一个简单的测试表,仅包含一个唯一键 col1:
CREATE TABLE `mytest` (
`id` varchar(20) DEFAULT NULL,
`col1` varchar(20) DEFAULT NULL,
UNIQUE KEY `uniq_col1` (`col1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
在 MySQL 5.7 环境中:
我们插入两条数据:
- testⅠ(罗马数字 Ⅰ)
- testI(英文字母 I)
执行结果:
mysql> insert into mytest values(1,'testⅠ');
Query OK, 1 row affected
mysql> insert into mytest values(1,'testI');
Query OK, 1 row affected
mysql> select * from mytest;
+------+---------+
| id | col1 |
+------+---------+
| 1 | testⅠ |
| 1 | testI |
+------+---------+
2 rows in set
插入成功,两条数据共存,且比较 ‘Ⅰ’ = ‘I’ 返回 0(不相等)。
在 MySQL 8.0.39 环境中:
同样的建表语句(注意:MySQL 8.0 默认使用 utf8mb4_0900_ai_ci 排序规则):
mysql> insert into mytest values(1,'testⅠ');
Query OK, 1 row affected
mysql> insert into mytest values(1,'testI');
ERROR 1062 (23000): Duplicate entry 'testI' for key 'mytest.uniq_col1'
插入失败!系统认为 ‘testⅠ’ 和 ‘testI’ 是重复的!
进一步验证:
mysql> select 'Ⅰ' = 'I';
+-------------+
| 'Ⅰ' = 'I' |
+-------------+
| 1 |
+-------------+
1 row in set
在 8.0 中,罗马数字 Ⅰ 和字母 I 被视为完全相同!
二、根本原因:排序规则(Collation)的演进
MySQL 5.7 和 8.0 对 utf8mb4 字符集的默认排序规则不同:
| 版本 | 默认 Collation | 特点 |
|---|---|---|
| MySQL 5.7 | utf8mb4_general_ci |
简单粗暴,按字节比较,区分度低 |
| MySQL 8.0 | utf8mb4_0900_ai_ci |
基于 Unicode 9.0 标准,支持语言敏感、重音不敏感、大小写不敏感,智能合并相似字符 |
关键点在于:utf8mb4_0900_ai_ci 将罗马数字 Ⅰ、Ⅱ、Ⅲ 等归一化为英文字母 I、II、III,因此在唯一索引中被视为重复。这不仅是罗马数字的问题,还包括:
- 全角/半角字符(如 ( vs ()
- 重音字符(如 é vs e)
- 特殊符号(如 ·、-、. 等在某些排序规则下可能被忽略或归一化)
三、影响范围与风险
- 这种行为变更在升级过程中可能引发严重问题:
- 数据丢失或插入失败:原本合法的数据在新库中被拒绝。
- 唯一索引失效:原本唯一的键在新排序规则下重复,导致索引无法建立。
- 应用逻辑异常:依赖字符串比较的业务逻辑(如用户登录、订单去重)可能出错。
- 迁移失败:使用 mysqldump 或数据同步工具时,可能在目标库报唯一键冲突。
四、解决方案:如何安全升级?
方案一:保持排序规则一致(推荐用于平滑迁移)
在升级前,显式指定表和字段的排序规则为 utf8mb4_general_ci,避免默认使用 utf8mb4_0900_ai_ci。
-- 建表时指定
CREATE TABLE `mytest` (
`id` varchar(20) DEFAULT NULL,
`col1` varchar(20) DEFAULT NULL,
UNIQUE KEY `uniq_col1` (`col1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
或修改已有表:
ALTER TABLE `mytest` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
方案二:应用层清洗数据(适用于新系统)
如果希望利用 8.0 的智能排序能力,需在应用层对输入数据进行标准化处理,可使用正则表达式检测非标准字符。
-- 我在生产系统迁移中,使用下面语句可以识别出非标字符,可借鉴、补充。
SELECT * FROM mytest
WHERE col1 REGEXP '[^a-zA-Z0-9_·.-\\u4e00-\\u9fa5]';
五、总结
MySQL 8.0 的 utf8mb4_0900_ai_ci 排序规则更智能、更符合国际化需求,但这种“智能”可能打破旧系统的数据假设。
在从 5.7 升级到 8.0 时,不要依赖默认排序规则,应主动评估并调整字符集与排序规则,避免因“看似相同”的字符导致数据不一致或业务中断。
下面是一些升级建议:
- 升级前全量扫描:使用正则或脚本扫描所有表中可能受排序规则影响的字段。
- 测试环境模拟:在测试库中使用 utf8mb4_0900_ai_ci 进行全量数据导入测试。
- 索引重建策略:升级后,对唯一索引进行 CHECK TABLE 或 OPTIMIZE TABLE。
- 应用层兼容性测试:重点测试用户注册、登录、订单创建等依赖字符串比较的流程。




