MySQL 里大小写不同的邮箱,为什么会撞上唯一索引?
在 MySQL 8.0 中,如果邮箱列使用 utf8mb4_0900_ai_ci,Dev@example.com 和 dev@example.com 比较时会被视为相等;给这列加上唯一索引后,第二个值就可能报重复键。决定字符串比较方式的是列的排序规则(collation),不是 utf8mb4 这个字符集名称。
字符集和排序规则分别管什么
可以把两者分开理解:字符集规定文本如何编码和存储;排序规则规定字符串如何比较、排序和分组。MySQL 8.0 的服务器默认字符集和排序规则是 utf8mb4 与 utf8mb4_0900_ai_ci,但数据库、表、列都可以单独设置,所以不能只看服务器默认值就断定某一列的行为。
排序规则名称里的后缀也能提供线索:_ci 表示比较时不区分大小写,_cs 表示区分大小写;_ai 表示不区分重音,_as 表示区分重音。比如 utf8mb4_0900_ai_ci 同时不区分重音和大小写,utf8mb4_0900_as_cs 则两者都区分。
为什么唯一索引也认为它们重复
下面的表明确把邮箱列设为不区分大小写,并对它建立唯一索引:
CREATE TABLE account (
id BIGINT PRIMARY KEY,
email VARCHAR(255)
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci NOT NULL,
UNIQUE KEY uk_account_email (email)
);
插入 Dev@example.com 后,再插入 dev@example.com,会触发唯一键冲突。原因是唯一索引要保证键值彼此不同,而这一列的排序规则把两个邮箱比较为相等。换句话说,索引本身没有忽略大小写;它遵循列的比较规则。
先查列的实际排序规则
查看建表定义或列信息,不要凭经验猜:
SHOW FULL COLUMNS FROM account;
结果中的 Collation 列能显示邮箱字段当前使用的排序规则。也可以看完整定义:
SHOW CREATE TABLE account;
如果列定义没有显式写 COLLATE,它可能继承表、数据库等层级的默认设置。迁移旧项目时,不同表甚至同一表的不同列都可能不同。
怎样选才符合账号规则
先定产品规则:系统是否把大小写不同的邮箱视作同一个账号?写入、登录查询和唯一性约束都应遵循同一规则。
- 大小写不同就应是同一账号:使用合适的大小写不敏感排序规则;应用端也应按同一规范处理输入。
- 大小写不同允许是不同账号:在 MySQL 8.0 中,可以将列改为大小写敏感的排序规则,例如
utf8mb4_0900_as_cs,并确认应用查询与唯一索引使用一致语义。 - 准备调整排序规则或唯一约束:先按目标排序规则检查现有数据是否会合并成重复键,再执行变更,避免上线时才遇到索引创建失败。
例如,下面的查询可以按目标的大小写不敏感规则找出冲突组:
SELECT email COLLATE utf8mb4_0900_ai_ci AS email_key,
COUNT(*) AS amount
FROM account
GROUP BY email COLLATE utf8mb4_0900_ai_ci
HAVING COUNT(*) > 1;
是否让邮箱区分大小写是业务决定,不要因为一次重复键报错就盲目切换排序规则。先确认账号如何判重,再同时核对字段排序规则、唯一索引和应用层的规范化逻辑。
**快速排查顺序:**查 SHOW CREATE TABLE → 确认列的 COLLATION → 明确账号判重规则 → 按目标规则检查历史数据 → 再调整列定义或索引。