SQL 主键与唯一约束设计方法

12次阅读

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

SQL 主键与唯一约束设计方法

主键和唯一约束到底该用哪个

主键是表的唯一标识,强制非空且全局唯一;唯一约束只保证字段值不重复,但允许 NULL。别图省事把业务字段(比如 email)直接设为主键——它可能为空、可能变更、还拖慢外键关联。主键优先选无业务含义的代理键,如 id SERIAL(PostgreSQL)或 id BIGINT AUTO_INCREMENT(MySQL)。

  • 主键自动创建唯一索引,且多数引擎只允许一个主键
  • 唯一约束可建多个,适合给多个业务字段加保护(如 phoneusername
  • 如果某列既要唯一又要允许空值,只能用唯一约束,不能用主键

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 就失去参照意义。这点在迁移老表结构时特别容易漏。

text=ZqhQzanResources