PostgreSQL 事务回滚后的序列副作用:为什么 ROLLBACK 无法重置自增主键的 last_value

发布时间:2026/10/8 1:57:27

PostgreSQL 事务回滚后的序列副作用:为什么 ROLLBACK 无法重置自增主键的 last_value 文档教程知识库【免费下载链接】til:memo: Today I Learned项目地址https://gitcode.com/gh_mirrors/ti/til点击查看免费下载导读PostgreSQL 的事务保证让回滚后一切仿佛从未发生成为可能这是在生产环境对重要UPDATE或批量写入做预演dry-run的底气所在。但回滚并非完全没有副作用以序列sequence为依托的自增主键即使事务被ROLLBACK其last_value也会永久向前推进留下可见的痕迹。本文基于仓库中 postgres/sequence-side-effect-when-rolling-back-inserts.md 的完整演示逐步验证这一行为剖析其背后的并发设计原因并给出高吞吐场景下的应对方案bigserial、identity column 与 UUID。事务回滚的零副作用预期与序列例外PostgreSQL 最强大的能力之一就是在一个事务内进行大大小小的各种变更然后整体回滚仿佛什么都没发生过。这在生产环境中极其实用想对一个关键的UPDATE语句做干跑验证时可以在事务内执行、检查受影响行数、查看表状态确认无误后再ROLLBACK——数据层面看起来与执行前完全一致。然而回滚即无事发生并非绝对真理。序列sequence就是那个会留下痕迹的例外。每向一张带有自增序列主键的表INSERT一行都会对序列产生一次推进即使这个事务随后被回滚序列的推进也不会撤销。本仓库的 sequence-side-effect-when-rolling-back-inserts.md 用一个books表的完整示例精确复现了这一过程。完整复现插入前、事务内、回滚后的序列状态对比第一步建表并检查序列初始状态首先创建一张使用bigserial自增主键的新表然后直接查看它背后的序列 create table books (id bigserial primary key, title text not null, author text not null); CREATE TABLE select * from books_id_seq; ┌────────────┬─────────┬───────────┐ │ last_value │ log_cnt │ is_called │ ├────────────┼─────────┼───────────┤ │ 1 │ 0 │ f │ └────────────┴─────────┴───────────┘ (1 row)序列以books_id_seq命名命名规则基于表名 列名 _seq。初始状态下last_value为1、log_cnt为0、is_called为ffalse表示尚未真正调用过。仓库中 renaming-a-sequence.md 也印证了这一点serial列会自动创建同名序列重命名表不会自动重命名序列需要手动ALTER SEQUENCE ... RENAME TO ...保持命名一致。第二步开启事务插入三行并再次检查序列 begin; BEGIN insert into books (title, author) values (Python in a Nutshell, Martelli et al), (Build an LLM, Sebastian Raschka), (The Joy of Abstraction, Eugenia Cheng); INSERT 0 3 select * from books; ┌────┬────────────────────────┬───────────────────┐ │ id │ title │ author │ ├────┼────────────────────────┼───────────────────┤ │ 1 │ Python in a Nutshell │ Martelli et al │ │ 2 │ Build an LLM │ Sebastian Raschka │ │ 3 │ The Joy of Abstraction │ Eugenia Cheng │ └────┴────────────────────────┴───────────────────┘ (3 rows) select * from books_id_seq; ┌────────────┬─────────┬───────────┐ │ last_value │ log_cnt │ is_called │ ├────────────┼─────────┼───────────┤ │ 3 │ 30 │ t │ └────────────┴─────────┴───────────┘ (1 row)三条新行的id分别是1、2、3序列的last_value随之推进到3is_called变为ttrue。值得注意的是log_cnt从0跳到30——这是序列内部为 WAL 日志预留的计数窗口用于减少日志同步频率属于实现细节不是本文关注的重点。第三步回滚事务对比表与序列的状态 rollback; ROLLBACK select * from books; ┌────┬───────┬────────┐ │ id │ title │ author │ ├────┼───────┼────────┤ └────┴───────┴────────┘ (0 rows) select * from books_id_seq; ┌────────────┬─────────┬───────────┐ │ last_value │ log_cnt │ is_called │ ├────────────┼─────────┼───────────┤ │ 3 │ 30 │ t │ └────────────┴─────────┴───────────┘ (1 row)结果一目了然books表已被清空回到 0 行但books_id_seq的last_value仍停留在3。回滚把表数据完整还原了却没有把序列倒带。下一次插入时id会从4开始而不是从1开始。原理剖析序列为何游离于事务隔离之外这个行为并非 bug而是 PostgreSQL 的刻意设计。序列被设计为在事务隔离之外工作同一时刻可能有多个并发事务都需要获取序列值如果让它们互相等待如传统加锁方式自增主键分配会成为高并发写入的瓶颈。因此序列总是可以被即时访问每个事务都能立刻拿到一个独占的值。代价正如 sequence-side-effect-when-rolling-back-inserts.md 所指出的序列的last_value只会单调向前推进marching forward永远不会后退。一旦某个事务回滚它消费掉的序列值就变成了永久空洞gap。因此序列值只保证唯一从不保证连续主键列上出现1、2、4、5这类缺口是正常现象3可能被回滚事务消费也可能被未来某个事务消费PostgreSQL 不回收已分配但未提交的值任何依赖id 必须连续无空洞的业务逻辑例如把 id 当序号展示、按 id 差值估算行数都是错误的假设。补充说明nextval的推进同样不回滚序列缓存值也类似。与序列不同的是普通数据表的行级变更受 MVCC 与 WAL 保护回滚后从任何视角都不可见而序列是全局共享、无版本化的计数器这正是二者行为差异的根源。相关序列管理操作速览理解了回滚不重置序列之后自然会关心如何主动重置序列。仓库中提供了两个直接相关的实践手动重启序列restart-a-sequence.md 展示了在开发/测试环境清空表数据后序列仍然从上次的位置继续递增可用以下命令将其重置回任意值 alter sequence my_table_id_seq restart with 1; ALTER SEQUENCETRUNCATE 时一并重置restarting-sequences-when-truncating-tables.md 指出对带serial主键的表执行TRUNCATE后后续插入会继续从上一次的值递增因为序列没有被重置。解决办法是追加restart identity让 PostgreSQL 自动把关联序列重置回1truncate pokemons, trainers, pokemons_trainers restart identity;序列归属change-the-owner-of-a-sequence.md 展示了在线迁移列类型如int换bigint时序列所有权不会自动转移需要ALTER SEQUENCE cats_id_seq OWNED BY cats.id;否则DROP COLUMN会因依赖报错。这些内容都与序列生命周期息息相关可作为本文主题的延伸阅读。高吞吐场景下的应对bigint、identity column 与 UUID原文明确指出这是正常且符合预期的行为但如果你维护的是高吞吐系统——持续大量插入并频繁回滚作者本人就维护过这样的系统——序列值的快速流失值得警惕。最直接的缓解手段是为序列支撑的列选择bigintbigserial。仓库中的 integers-in-postgres.md 详细列出了三种整数类型的容量类型字节范围smallintint22-32768 32767integerint44-2147483648 2147483647bigintint88-9223372036854775808 9223372036854775807bigint的上限约为 9.2×10¹⁸足以在大量插入 频繁回滚的组合下让跑完序列值这个担忧变得不再现实。原文即采用bigserial定义books.id演示与结论在bigint尺度下依旧成立。另一条路径是改用 UUID 作为主键彻底摆脱对序列的依赖也就自然不存在序列值被回滚事务消费的问题。仓库中的 generate-a-uuid.md 与 generating-uuids-with-pgcrypto.md 提供了两种生成方式可在建表时用 UUID 直接代替自增整数主键。最后对于新项目还可以关注现代做法PostgreSQL 官方 wiki 已明确建议Dont use serial转而使用 identity columnGENERATED ... AS IDENTITY。仓库中的 generate-modern-primary-key-columns.md 给出了两种写法的对照——identity column 在 schema、依赖与权限管理上更加规范但它本质上仍由序列驱动同样存在回滚后序列推进的副作用所以本文的核心结论对它依然适用。小结事务回滚能完美还原表数据但不能还原序列ROLLBACK后books_id_seq.last_value仍停留在3这就是序列留在事务隔离之外的代价。序列只保证唯一性不保证连续性id出现空洞是高并发与回滚场景下的正常现象。高吞吐写入 频繁回滚的系统应优先选用bigserialbigint扩大序列容量上限或改用 UUID 主键彻底规避该问题。赞分享文档教程知识库【免费下载链接】til:memo: Today I Learned项目地址https://gitcode.com/gh_mirrors/ti/til点击查看免费下载相关推荐KeyStore Explorer架构揭秘GUI框架与加密模块深度解析KeyStore Explorer架构揭秘GUI框架与加密模块深度解析 KeyStore Explorer是一款免费的GUI工具可替代Java命令行工具kePostgreSQL 序列重置实战用 ALTER SEQUENCE ... RESTART 精确控制自增主键起点PostgreSQL 序列重置实战用 ALTER SEQUENCE ... RESTART 精确控制自增主键起点 本文聚焦 PostgreSQL 中序列se文档教程知识库SQLDelight JVM PostgreSQL 事务指南transaction / transactionWithResult / Rollback 与回调详解SQLDelight JVM PostgreSQL 事务指南transaction / transactionWithResult / Rollback后端ORM上一篇lcobucci/jwt 令牌验证完全指南assert()、validate() 与全部内置约束详解下一篇Blender 3MF插件专业级3D打印工作流完整解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/8 1:57:27

OCaml 版本号、发布周期与发布流程全解析(ocaml/ocaml 仓库)

编程语言编译器语言运行时标准库 【免费下载链接】ocaml The core OCaml system: compilers, runtime system, base libraries 项目地址: https://gitcode.com/gh_mirrors/oc/ocaml 点击查看 免费下载 OCaml 的版本号遵循 Linux 风格的语义化方案,其含义…

2026/10/8 2:57:34

HDFS底层原理与生产运维实战:从架构到故障排查

写这篇文章之前,我刚帮一位读者排查了一个盘符写满导致的DataNode宕机问题,顺手翻了翻他给的集群监控截图,三副本策略下整整丢了近一小时的写入数据。这不是个例——很多人把HDFS当成一个"能存大文件的分布式硬盘"来用,…

2026/10/8 2:57:34

CoreConsultant实用指南:从IP配置到SoC集成与调试

做芯片前端的人,几乎都绕不开Synopsys这一整套EDA工具链。Design Compiler做综合,VCS做仿真,Verdi看波形,这些名字天天挂在嘴边。但有一个工具,平时存在感不高,真正用起来却能省掉大量重复劳动,…

2026/10/8 2:57:34

C#实现SQL Server自动建表:从T-SQL拼接到反射与EF Core迁移

简介:一份面向C#开发者的SQL Server自动建表工具源码包,解决通过文本文件导入自动生成表结构、并将中文字段转为拼音首字母的实际需求,适合数据导入、系统初始化或需兼容中英文环境的数据库管理场景。压缩包内共30个文件,体积约97…

2026/10/8 2:57:34

双向可控硅调功必须过零触发:原理、丢波与斩波实战指南

1. 为什么“过零检测”不是可选项,而是双向可控硅调功的生死线你手头那块刚焊好的双向可控硅板子,接上灯泡一试——灯丝滋啦一声就断了;换上电炉丝,温度忽高忽低像在跳舞;连最简单的风扇调速,转速表指针都在…

2026/10/8 2:57:34

TDX板块指数复盘系统:从数据清洗到ECharts可视化的完整实践

做盘后复盘这件事,我吃了很久的苦头。每天收盘后对着行情软件来回切板块、翻个股,靠肉眼对比谁在领涨、谁在拖后腿,再用Excel手工记流水账,一套流程下来至少一个小时,而且第二天还要重来。后来我干脆自己动手&#xff…

2026/10/8 2:52:33

MySQL+Flask+ECharts:从数据查询到可视化看板的完整实战

1. 整体设计思路拆解:从数据库到浏览器,一条数据流水线1.1 可视化不是“画图”那么简单先说个扎心的现实:SQL写得再溜,如果数据只能在终端里滚动输出,老板和业务同事根本不会被打动。我见过太多团队花大力气维护MySQL数…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/7 8:18:33

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/6 17:46:51

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 0:02:17

自然数立方等于连续奇数之和:从证明到编程验证

十几年来我一直游走在数学科普和编程教学这两块内容之间,对“看起来像魔法、拆开全是数学”的结论总是格外敏感。最近翻资料时又撞见一句话:任何一个自然数 m 的立方,都可以写成 m 个连续奇数之和。2 的立方等于 3 加 5,3 的立方等…

2026/10/8 0:02:17

C#上位机SSH连接实战:用SSH.NET补齐超时、批量与密钥认证

简介:这是一份基于 C# 开发的 SSH 连接功能半成品工程,原本作为另一个主项目的子功能模块,现独立打包分享。工程采用 WinForms 界面,包含源码、解决方案、安装部署工程、NuGet 依赖包及说明文档,适合正在做远程连接、网…

2026/10/8 0:02:17

Java SpringBoot一体化智能售后系统设计与实现全解析

毕业设计年年做,Java Web 方向的题目翻来覆去就那么几个,但“一体化智能售后系统”这个题,每次看到我都觉得值得认真聊一聊。它不是一个简单 curd 堆出来的管理系统,而是把客户、工单、派单、处理、回访、统计整条链路串起来的一套…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑