必须先清理重复数据,否则alter table add unique (c1, c2)会因数据冲突报错1062;应显式命名约束如add constraint uk_c1_c2 unique (c1, c2),便于验证、删除和排查,且需手动用show index和重复插入测试确认生效。

必须先清理重复数据,否则命令直接失败;显式命名约束比匿名写法更可控、可维护。
ALTER TABLE ADD UNIQUE (c1, c2) 为什么总报错 Duplicate entry
这不是语法问题,而是数据冲突。MySQL 在加联合唯一约束时,会尝试为 (c1, c2) 组合建立唯一 B+ 树索引,一旦发现已有两行值完全相同(比如 ('a', 'b') 出现两次),就会中断并抛出错误:Duplicate entry 'a-b' for key 'uk_c1_c2'。
常见错误现象:
- 执行
ALTER TABLE t ADD UNIQUE (c1, c2)后卡住或报错 1062 - 误以为加了约束就生效,结果插入重复数据没拦住——其实是根本没加成功
正确做法:
- 先查重复:
SELECT c1, c2, COUNT(*) FROM t GROUP BY c1, c2 HAVING COUNT(*) > 1; - 根据业务决定删哪条(通常保留
id最小的):DELETE t1 FROM t t1 INNER JOIN t t2 WHERE t1.c1 = t2.c1 AND t1.c2 = t2.c2 AND t1.id > t2.id; - 再执行加约束语句——别跳过这步
该用 ADD CONSTRAINT uk_xxx UNIQUE 还是 ADD UNIQUE KEY
推荐始终用显式命名语法:ALTER TABLE t ADD CONSTRAINT uk_c1_c2 UNIQUE (c1, c2);。虽然 ADD UNIQUE KEY (c1, c2) 在多数版本能跑通,但有隐患:
- MySQL 会自动给匿名约束起名(如
c1),如果c1列名重复或改名,后续删约束时容易搞错对象 - 约束名用于
SHOW INDEX和错误提示,命名清晰便于排查(比如看到uk_user_role就知道是用户角色组合) - 删除时必须匹配名字:
ALTER TABLE t DROP INDEX uk_c1_c2;—— 注意不是DROP CONSTRAINT,MySQL 不支持这个语法
命名建议:uk_ 前缀 + 字段缩写(如 uk_order_no_shop_id),避免空格、特殊字符,长度控制在 64 字符内。
NULL 值会让联合唯一失效,但你可能没意识到
MySQL 认为 NULL != NULL,所以 (1, NULL) 和 (1, NULL) 不算重复,能同时插入。这常导致业务逻辑漏洞——比如“用户 ID + 设备 Token”本应唯一,但某次设备未上报 Token(存为 NULL),就漏放行了。
应对方式:
- 如果业务上这些字段本就不该为空,先补
NOT NULL:ALTER TABLE t MODIFY c2 VARCHAR(32) NOT NULL; - 如果确实要允许部分字段为
NULL,且仍需去重,得换方案:用生成列(MySQL 5.7+)或应用层兜底校验 - 测试时别只插非空值,一定要试
(1, NULL)和(1, NULL)是否被拦住
加完之后怎么确认它真起了作用
加约束命令执行成功 ≠ 约束已生效。MySQL 不会主动告诉你“已建好”,必须手动验证:
- 查索引是否存在:
SHOW INDEX FROM t WHERE Key_name = 'uk_c1_c2';—— 应返回一行,且Non_unique列为0 - 实测插入重复数据:
INSERT INTO t (c1, c2) VALUES ('x', 'y'), ('x', 'y');—— 第二条必须报错 1062 - 注意性能影响:该约束会自动建唯一索引,对
WHERE c1 = ? AND c2 = ?查询有加速作用,但高并发写入时会有轻微延迟(B+ 树分裂开销)
最容易被忽略的是:约束名和索引名是一体的,删的时候只能用 DROP INDEX,且必须确保没有其他约束(比如主键、外键)依赖同一列组合——否则会连带触发检查失败。











