唯一索引冲突本质是“语义重复”而非肉眼可见的重复,主因包括:字符集与排序规则(collation)隐式转换导致比较结果相等(如'cafe'与'café'在_ci规则下视为相同)、多唯一索引下ON DUPLICATE KEY UPDATE未覆盖所有冲突列、NULL值虽不参与唯一校验但默认值或隐式转换可能引发意外冲突、以及隐式类型转换使不同形式输入被判定为相同值。

唯一索引冲突不是“重复插入”,而是“语义重复”
MySQL报 ERROR 1062: Duplicate entry,往往不是你肉眼看到两行数据一模一样,而是索引列的计算结果被判定为相等。最常见原因是字符集和排序规则(collation)隐式转换——比如 utf8mb4_0900_as_cs 区分大小写和重音,而 utf8mb4_general_ci 不区分。同一字段存 'cafe' 和 'café',在 _ci 规则下会被视为重复。
- 用
SHOW CREATE TABLE table_name查看索引列实际使用的 collation - 用
SELECT COLLEGE(column_name) FROM table_name验证值在当前 collation 下是否真相同 - 避免在唯一索引列上混用不同 collation 的字段(例如 JOIN 时隐式转换)
INSERT ... ON DUPLICATE KEY UPDATE 仍报错?检查是否命中“非主键唯一索引”
很多人以为只要写了 ON DUPLICATE KEY UPDATE 就能兜底,但 MySQL 只对“触发冲突的那一个唯一约束”执行更新。如果表上有多个唯一索引(比如 email 和 phone 都建了 UNIQUE),而你 INSERT 的数据只撞了 phone 索引,但 ON DUPLICATE KEY UPDATE 里没包含 phone 列的赋值逻辑,就会直接报错,而不是跳过或忽略。
- 确认冲突由哪个索引引发:错误信息末尾会带具体键值,如
Duplicate entry '138****1234' for key 'idx_phone' -
ON DUPLICATE KEY UPDATE必须显式覆盖所有可能冲突索引涉及的列,否则不生效 - 若只想按主键更新、忽略其他唯一键冲突,改用
INSERT IGNORE或应用层先SELECT
NULL 值在唯一索引里不算“重复”,但可能意外触发冲突
标准 SQL 规定:唯一索引允许任意数量的 NULL,因为 NULL != NULL。但注意两个陷阱:
- 如果你用的是
NOT NULL字段 + 默认值(比如DEFAULT ''或DEFAULT 0),空字符串或零会被当真实值参与唯一判断 - JSON 字段建唯一索引时,
JSON_CONTAINS或表达式索引若返回NULL,该行仍可重复插入——但一旦表达式产出非 NULL 值,就进入唯一校验 - 某些 ORM(如 Laravel Eloquent)在批量插入时自动补
NULL值,若字段定义为UNIQUE且无NOT NULL,多条NULL不冲突;但加上NOT NULL后,ORM 补的默认空值就可能撞车
隐式类型转换导致“看起来不同,实则相同”
当你对字符串类型的唯一索引列插入数字(如 INSERT INTO user (code) VALUES (123)),而 code 是 VARCHAR,MySQL 会把 123 转成字符串 '123';但如果已有 '0123' 或 '123 '(带空格),是否冲突取决于字段的 collation 和是否启用了 pad_char_to_full_length。
- 用
SELECT HEX(column_name)查看实际存储字节,比肉眼判断更可靠 - 避免在唯一索引列上依赖隐式转换:插入前统一转成字符串并
TRIM(),或改用BINARY类型列 - 整数型唯一索引列,别往里插字符串(如
'123'),尤其当列是INT时,MySQL 会截断或报错,行为因 sql_mode 而异


















