ALTER TABLE ADD UNIQUE 失败主因是列存重复值,MySQL 5.7+ 严格校验;需先查重、清理或改用组合索引;显式命名约束更易维护;大表应优先用 ALGORITHM=INPLACE;NULL 在唯一索引中不互斥,业务需额外处理。

ALTER TABLE ADD UNIQUE 索引失败:常见报错和原因
直接执行 ALTER TABLE t ADD UNIQUE (col) 报错 Duplicate entry 'xxx' for key 'col',说明该列已有重复值。MySQL 不允许在存在重复数据的列上直接加唯一约束——它不是“忽略重复”,而是“拒绝创建”。这和 PostgreSQL 的 CREATE INDEX CONCURRENTLY 或带 IGNORE 的旧版 MySQL 语法不同,5.7+ 默认严格校验。
实操建议:
- 先查重:
SELECT col, COUNT(*) FROM t GROUP BY col HAVING COUNT(*) > 1 - 确认是否真要保留所有行:若业务允许去重,用
DELETE t1 FROM t t1 INNER JOIN t t2 WHERE t1.id > t2.id AND t1.col = t2.col(需有主键) - 若不能删数据,考虑改用组合唯一索引(如
(col, create_time)),或引入逻辑删除字段绕过冲突
UNIQUE 约束 vs UNIQUE INDEX:选哪个?
在 MySQL 中,ADD UNIQUE 和 ADD UNIQUE INDEX 效果几乎一样:都会创建唯一 B-tree 索引,并隐式添加约束。但语义和后续行为有细微差别:
- 用
ADD CONSTRAINT uk_col UNIQUE (col)显式命名约束,后续删约束时能精准定位:DROP INDEX uk_col ON t或ALTER TABLE t DROP INDEX uk_col - 若只写
ADD UNIQUE (col),MySQL 自动生成约束名(如t_col_unique),名字难记且跨环境不一致 - 约束名在错误提示里会直接出现,比如
Duplicate entry 'a' for key 'uk_col'比't_col_unique'更易排查
线上表加唯一索引卡住或锁表太久怎么办?
对大表(千万级+)执行 ALTER TABLE 加唯一索引,默认会触发全表重建(ALGORITHM=COPY),期间表不可写,还可能因临时空间不足失败。
实操建议:
- 优先用
ALGORITHM=INPLACE(5.6+ 支持):ALTER TABLE t ADD UNIQUE INDEX uk_col (col) ALGORITHM=INPLACE, LOCK=NONE;但注意:只有「新增索引」支持无锁,「新增约束并验证历史数据」仍需扫描全表 - 若表太大,分两步走:先建普通索引(
ADD INDEX),再用应用层校验数据唯一性,最后改名升级为唯一索引 - 避开高峰期,监控
information_schema.INNODB_TRX和SHOW PROCESSLIST,防止长事务阻塞 DDL
NULL 值在 UNIQUE 索引里的表现很反直觉
MySQL 允许唯一索引列存多个 NULL,因为 NULL != NULL。这意味着 INSERT INTO t (col) VALUES (NULL), (NULL) 能成功——它不违反唯一性。
实操建议:
- 如果业务上「空值也必须唯一」,不能依赖 UNIQUE 索引,得在应用层拦截,或用生成列 + 唯一索引:
ALTER TABLE t ADD COLUMN col_nn VARCHAR(32) STORED AS (IFNULL(col, CONCAT('NULL_', UUID_SHORT()))),再对col_nn加唯一索引 - 检查现有数据时,记得把
IS NULL单独查出来:SELECT COUNT(*) FROM t WHERE col IS NULL - 文档里写的 “NULL values are not considered equal” 是 MySQL 行为,PostgreSQL 和 SQL Server 处理方式不同,迁移时容易踩坑


















