已分配的序列值可能留下空洞,存储行的唯一性由主键单独保证。

序列分配数值,不随事务回滚回收

PostgreSQL 序列分配数值,并不以调用方事务最终提交为前提。这让并发场景中的普通标识分配更实用,也意味着空洞是正常现象。一个序列号只说明发生过分配,不能证明对应行存在、事务已经提交,或所有更小的数值都已经使用。把取号与业务记录的生命周期分开,才能正确解释缺号和顺序。 原子分配也有前提 nextval 会原子推进序列。普通非循环序列在没有被重置到已用范围的情况下,能为并发调用方分配不同数值。这不代表实现完全不使用锁,也不代表任何管理操作都不会影响它。CYCLE 和显式重置都可能重新使用数值,因此,已存储行的唯一性仍应由表上的 PRIMARY KEY 或 UNIQUE 约束负责。 序列也有有限边界。非循环序列无法在范围内继续分配时会报错,不会自动扩张成无限整数。应明确选择底层类型和步长;高频标识接近配置上限时,要关注剩余容量。即使序列使用更宽的类型,也不会自动扩宽接收它的表字段,不能只检查分配器而忽略目标列的类型限制。 identity 和 serial 列通过序列提供默认值。序列负责分配,字段约束负责决定哪些行允许写入。显式导入的标识可能让分配器落后于表中数据。修复这种错位时,要考虑活跃写入者和已经预留的数值,不能在业务仍并发写入时,随意根据一次 max 查询执行 setval,然后假设风险已经消失。 空洞与顺序是不同属性 事务回滚不会收回 nextval 已分配的数值。取号后没有使用、插入中止,或 INSERT 走了 ON CONFLICT 分支,都可能产生空洞。删除表中一行也不会让序列倒退。因此,单凭编号缺失,不能认定记录丢失或序列损坏;需要结合事务和业务记录查明实际含义。 CACHE 会按会话预留一批数值。如果一个会话保留一到十,另一个保留十一到二十,外部观察的调用结果就可能是一、十一、二。会话结束时,未使用的预留值也可能被放弃。缓存提高分配效率,却会影响连续性和全局观察顺序;即使没有批量缓存,分配先后也不等于事务提交先后。 setval 对序列状态的修改不会因调用方回滚而撤销,其他会话还可能持有原先缓存的数值,所以重置活跃分配器不是安全的消除空洞方法。如果业务要求连续编号,应明确设计事务内分配与取消语义。这可能需要串行访问一个专用计数器,但不意味着必须锁住业务表中的每一行。不能把普通技术主键与这种业务编号混为一谈。 坏掉的样子 currval 保存指定序列的会话局部值,通常由 nextval 建立。本会话尚未建立这个值时,调用会报错;is_called 为真的 setval 也会修改该会话值。其他连接取过号,并不能为当前连接初始化它。使用连接池时,如果 nextval 与 currval 落到不同数据库会话,原本看似连续的两步操作就可能失败。 ...

四月 12, 2027 · 2 分钟 · XB Field Notes