代理键把身份与含义分开
代理主键把一行记录的身份与可能变化的业务值分开。客户修改邮箱时,订单仍引用同一个客户编号,而不必跟着邮箱一起改引用。这对于易变或较宽的业务键很有用,但稳定性来自数据库约束与应用规则,不是“自动生成”四个字本身。一个生成的数字不会天然不可修改、保密、全局唯一,也不能代替访问授权。 稳定标识来自规则与约束。生成机制、合法存储值、业务规则、引用和访问决定需要分别保证。 生成身份与约束唯一性 PostgreSQL identity 列从关联序列取得默认值。GENERATED ALWAYS AS IDENTITY 会拒绝普通显式赋值,GENERATED BY DEFAULT 则允许调用方提供值。即使使用 ALWAYS,导入时仍可通过 OVERRIDING SYSTEM VALUE 显式覆盖。两种形式都不会单独保证值唯一;需要拒绝重复标识时,仍须声明 PRIMARY KEY 或 UNIQUE 约束。 主键同时保证唯一与非空,并提供可供引用使用的索引。Identity 负责怎样生成默认值,约束负责哪些最终值允许存储,这两项职责不能混淆。导入时尤其如此:显式写入一个很大的编号,不会自动把序列推进到那个位置。若没有协调序列状态,后续生成值可能最终与导入行相撞,直到主键报错才暴露问题。 单独保留业务唯一规则 代理编号不会阻止相同业务实体重复出现。如果邮箱是唯一登录标识,应继续保留明确的唯一约束,并先决定规范化规则。普通 text 的 UNIQUE 使用该列的比较语义,不会暗中替应用处理大小写折叠、空白或各种邮箱等价规则。迁移之前就要明确这些行为,否则数据库认为不同的值,应用可能认为是同一账号,或者反过来。 子表如果不能出现悬空引用,就需要真实的外键约束。邮箱不是引用键时,修改它不会改变订单指向的客户,因为订单保存的是 id。这缩小了这一项修改的范围,却不能承诺所有业务改名都变成简单操作:搜索索引、审计记录、外部集成和缓存仍可能依赖邮箱,需要按实际调用链更新。数据模型只解决它真正覆盖的关系。 ...