主键应选无业务含义的代理键,强制非空且全局唯一;唯一约束可多个、允许null,适合多业务字段保护,但mysql与postgresql对null处理不同,联合约束需按业务语义和查询频率设计。

主键和唯一约束到底该用哪个
主键是表的唯一标识,强制非空且全局唯一;唯一约束只保证字段值不重复,但允许 NULL。别图省事把业务字段(比如 email)直接设为主键——它可能为空、可能变更、还拖慢外键关联。主键优先选无业务含义的代理键,如 id SERIAL(PostgreSQL)或 id BIGINT AUTO_INCREMENT(MySQL)。
- 主键自动创建唯一索引,且多数引擎只允许一个主键
- 唯一约束可建多个,适合给多个业务字段加保护(如
phone、username) - 如果某列既要唯一又要允许空值,只能用唯一约束,不能用主键
MySQL 和 PostgreSQL 对 NULL 的处理差异
这是最容易翻车的地方:MySQL 在唯一约束下,多个 NULL 被视为“互不相等”,所以可以插多条 NULL;而 PostgreSQL 把所有 NULL 当作“相等”,因此唯一约束下只允许一个 NULL 或零个。
- MySQL:
INSERT INTO users (email) VALUES (NULL), (NULL);成功 - PostgreSQL:同上语句会报错
ERROR: duplicate key value violates unique constraint - 想在 PG 实现类似 MySQL 行为?得用函数索引:
CREATE UNIQUE INDEX idx_email_not_null ON users (email) WHERE email IS NOT NULL;
联合唯一约束怎么写才不踩坑
联合唯一约束不是简单把几个字段堆一起,关键看业务语义是否真需要“组合唯一”。比如订单表里 (user_id, order_no) 可能合理,但 (created_at, status) 几乎没意义。
- 顺序影响索引效率:高频查询条件放前面,比如常查
WHERE user_id = ? AND status = ?,那约束就该是UNIQUE (user_id, status),反过来效果差 - 注意隐式类型转换:MySQL 中
CHAR(10)和VARCHAR(10)在联合约束里可能因填充空格导致误判重复 - 不要对含
TEXT或大字段建联合唯一约束——大多数数据库不支持,或要求前缀长度限制(如 MySQL 的UNIQUE (content(255)))
删除主键或唯一约束时为什么报错
常见错误是只记得删约束名,却忘了它背后绑着索引。尤其在 MySQL 中,删主键会连带删掉聚簇索引,有时还会卡住;PostgreSQL 则更严格:删约束前得先确认有没有依赖它的外键或视图。
- MySQL 删除主键:
ALTER TABLE users DROP PRIMARY KEY;—— 必须确保没有自增列依赖它,否则失败 - PostgreSQL 删除唯一约束:
ALTER TABLE users DROP CONSTRAINT users_email_key;—— 约束名得查pg_constraint或用\d users看清真实名字 - 别想当然用
DROP INDEX直接删约束背后的索引,PG 会拒绝,MySQL 可能破坏约束完整性
外键引用的必须是主键或有唯一约束的列,但很多人忘了唯一约束列还必须是非空(除非显式允许 NULL),否则外键指向一堆 NULL 就失去参照意义。这点在迁移老表结构时特别容易漏。










