五值预留条标出已返回的一、三,回滚的二及从未返回的四、五;退出放弃括号连接下个后端从六开始。

Postgres 序列用一个缓存发号

PostgreSQL 序列缓存为数据库后端预留数值,不属于某个应用对象,也不只属于一笔事务。理解这个边界,才能解释为什么序列保存的位置会领先于真正返回的号码,为什么重新连接也会留下空洞,以及为什么调小 CACHE 不能让标识连续。首先要区分分配器状态,与应用真正成功提交的业务记录,不能把两者当成同一个计数器。 三个不同的数字 一个新建的升序序列使用 CACHE 5 时,第一次 nextval 可以返回一,而 last_value 已记录五。后者是预留边界,不是已插入行数,也不表示一到五都已经交给应用。currval 表示当前会话最近为该序列建立的值,其他连接取号不会替换它。监控时若不说明这些字段的作用域,就很容易把预留数量误当成业务数量。 同一后端后续取号,可以使用已经预留的数值。PostgreSQL 实现中既有后端局部的快速路径,也有补充缓存时的缓冲锁;把每次 nextval 都说成对普通表记录加一把行锁并不准确。缓存减少反复访问序列状态,但不能据此认定某个应用的插入延迟主要来自序列竞争。修改参数前,应先测量真实瓶颈,而不是根据取号机制猜测整体性能。 会话寿命也影响空号 即使正常退出,会话结束时没有用完的预留值也会被放弃。因此,相同取号速率下,频繁建立数据库会话的服务,可能比使用长期连接池的服务放弃更多预留。缓存跟随服务器会话;应用看到一个连接对象,并不足以说明底层数据库会话是否一直存在,连接复用策略也是实验条件的一部分。 不同会话还可以分别持有不同区间。一个先返回一,另一个返回六,第一个随后再返回二。这是分配交错,不代表提交顺序,也不能证明这些值已经丢失。即使 CACHE 为一,事务仍可能先取号后提交,顺序与另一个事务相反。提交之前写入的时间戳同样不能自动提供真实提交顺序,不能只给字段换个名字就获得新的保证。 坏掉的样子 不要把 CACHE 当成崩溃后最大跳号数的公式。多个会话可以同时持有预留,而实现还会把一部分未来序列进度提前写入日志。事务回滚、走冲突处理分支的插入,以及取到但没有使用的值,都会增加空洞。缺号本身既不能证明丢了业务数据,也不能证明没有丢;发现故障时,应核对已提交记录和恢复证据,再判断是否只是分配器行为。 正常运行期间,非循环分配在管理员没有把序列重置回已用范围时,会提供不同数值。但循环、手工重置,以及显式导入编号,都可能改变这个前提。表仍然需要主键或唯一约束保证存储完整性。减小缓存不能代替这些约束,不能找回已经删除的记录,也不会自动让业务编号流程具备可审计性。在数据库外持久使用一个号码之前,应先提交取号事务;崩溃可能使尚未提交的序列变化是否保留变得不确定。 调整序列设置时,还要考虑活跃会话已经拿到的预留值。不要为了补洞,在并发写入期间把序列随意重置到表中最大编号。如果业务确实要求已签发号码连续,应先明确签发、回滚、取消和外部发布的边界,再评估事务计数器。没有经过实际负载测量,就不存在可以普遍承诺的最佳缓存大小或吞吐提升倍数。 已返回的二与从未返回的四、五是不同空号来源。历史原样脚本仅覆盖先后单用户后端,不证明崩溃跳号或并发。 快速验证 下面的无套接字练习,使用两个先后运行的单用户后端,访问同一个新初始化的可丢弃集群。以非 root 账户执行脚本,并把 PostgreSQL 18 的 initdb 与 postgres 放入 PATH。绝不能让单用户模式访问正在运行的集群。该模式具有隐式超级用户权限,不能验证并发客户端、真实进程间锁竞争或故障切换;本例只研究缓存预留的生命周期。 ...

八月 2, 2027 · 2 分钟 · XB Field Notes
分配的一、二、三位于存储的一、三上方;回滚后存储二的位置用红叉标记。

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

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

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