复合唯一约束和复合唯一索引在功能上等价,但语义不同:约束是完整性规则,索引是查询加速结构;创建约束会自动创建同名唯一索引,而单独建索引不产生约束;外键引用必须依赖约束而非仅索引。

复合唯一约束和复合唯一索引是一回事吗
在 MySQL 中,UNIQUE 约束和 UNIQUE INDEX 在行为上高度一致:都强制多列组合值唯一、都自动创建 B+ 树索引、都支持 NULL(允许多个 NULL,因为 NULL != NULL)。但它们的 DDL 语义和管理方式不同:CONSTRAINT uk_name UNIQUE (a, b) 是标准 SQL 约束语法,删除需用 ALTER TABLE DROP INDEX uk_name;而 CREATE UNIQUE INDEX idx_ab ON t(a,b) 是索引语法,删除也用 DROP INDEX idx_ab。实际效果一样,但命名、查错、维护路径不同。
建表时直接定义复合唯一约束(推荐)
最清晰、最易维护的方式是在 CREATE TABLE 语句中用表级约束语法显式命名:
CREATE TABLE user_roles ( user_id INT NOT NULL, role_id INT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uk_user_role UNIQUE (user_id, role_id) );
- 必须用
CONSTRAINT显式命名,否则 MySQL 自动生成类似user_roles_ibfk_1的乱码名,后续删改极难定位 - 列顺序很重要:查询条件必须满足「最左前缀」才能命中索引,比如
WHERE user_id = ?可用,但WHERE role_id = ?不会走这个索引 - 允许
(1, NULL)和(1, NULL)同时存在——MySQL 认为两个NULL不相等,不违反唯一性
给已有表添加复合唯一约束
对已上线的表,用 ALTER TABLE ... ADD UNIQUE KEY 添加,但要注意索引名来源:
- 如果写
ALTER TABLE user_roles ADD UNIQUE (user_id, role_id);,MySQL 会默认把第一个字段名user_id当作索引名,即生成索引user_id - 更稳妥的是显式指定名:
ALTER TABLE user_roles ADD UNIQUE KEY uk_user_role (user_id, role_id); - 添加前务必确认数据无重复,否则命令直接报错:
ERROR 1062 (23000): Duplicate entry '1-2' for key 'uk_user_role' - 可通过
SELECT user_id, role_id, COUNT(*) FROM user_roles GROUP BY user_id, role_id HAVING COUNT(*) > 1;预检重复
删掉它比加它还容易出错
删除复合唯一约束本质是删索引,但很多人卡在找不到索引名:
- 别猜,先查:
SHOW INDEX FROM user_roles;,看Key_name列对应值(不是Column_name) - 确认后执行:
ALTER TABLE user_roles DROP INDEX uk_user_role; - 如果当初没命名,且 MySQL 自动生成了
user_id这样的名,要小心——可能和其他单列索引同名,误删风险高 - 注意:删除后不会自动删掉对应的
CONSTRAINT元数据,但约束已失效;information_schema.table_constraints中该记录仍存在,需手动清理(极少需要)


















