PostgreSQL可重复读和可序列化有什么区别?

来源:搜索优化作者:Robin头衔:草根站长
导读:本期聚焦于Robin创作的《PostgreSQL可重复读和可序列化有什么区别?》,敬请观看详情。并发事务中同时出现读取偏差和写入冲突时,可重复读和可序列化经常被混为一谈。但在PostgreSQL里,这两者并不能互相替代。可重复读依赖MVCC快照,保证同一事务内多次查询看到相同数据,不会产生不可重复读和幻读,却无法识别写偏斜这种因读旧数据而做出的冲突写入。可序列化在快照基础上增加SSI冲突检测,能够发现事务之间的读写依赖环,主动回滚可能破坏串行化调度的一方。本文从隔离级别定义、异常现象、冲突检测机制和实际SQL表现几个方面,拆解两者的具体差别。通过医生值班表等并发写入案例,读者可以直观看到可重复读漏掉的写偏斜问题,以及可序列化如何通过依赖检测和事务回滚来保证业务约束。文章最后分析性能开销和适用场景,说明为什么在高并发对账、库存扣减等场景中需要升级到可序列化。

一、PostgreSQL事务隔离模型:从快照说起

PostgreSQL的隔离级别并没有完全照搬SQL标准中的异常定义,而是通过MVCC和快照机制实现了一套独特的行为。读已提交和可重复读都依赖快照,只是快照的建立时机不同。读已提交每条语句都会获取新的快照,而可重复读在事务的第一条SQL执行时建立快照,并一直使用到事务结束。可序列化则是在可重复读快照的基础上,增加了一层基于读写依赖的冲突检测。

PostgreSQL可重复读和可序列化有什么区别?

许多资料会把可重复读等同于防止不可重复读,把可序列化等同于防止幻读。但在PostgreSQL中,可重复读并不会出现幻读,因为整个事务共用同一个快照,其他事务插入的新行对本事务不可见。真正的区别在于写偏斜这类由读取旧数据导致的约束冲突,只有可序列化能够主动捕获并处理。

理解这一点的关键在于认识到MVCC只解决数据可见性,不解决事务调度的可串行性。可重复读保证了读一致性,但没有维护读写之间的可串行化依赖。可序列化则通过Serializable Snapshot Isolation(简称SSI)检测是否存在可能破坏串行化调度的依赖环,并在必要时回滚事务。

二、可重复读的行为边界与写偏斜问题

可重复读级别下,事务内多次读取同一批数据不会发生不可重复读,也不会看到其他事务提交的新行。这个特性非常适合报表统计、对账查询等只需要读取一致快照的场景。但它的保护范围主要集中在写与写之间的直接冲突,当事务基于读取结果做出写入决策时,可重复读无法判断这个决策是否因为读取了过期数据而变得不安全。

写偏斜是最典型的例子。两个事务分别读取了符合条件的数据,都得到了可以继续写入的结论,然后各自写入不同行,但合并起来却违反了业务约束。可重复读不会阻止这种操作,因为两个事务修改的是不同行,不存在直接的写冲突。

下面用医生值班表说明问题。业务要求同一个日期最多只能安排一名医生值班,但表上没有唯一约束,而是依靠应用层检查。两个事务同时查询某天是否有值班记录,都发现没有,然后各自插入一条记录。

-- 事务 A
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT COUNT(*) FROM duty_schedule WHERE duty_date = '2024-06-01';
-- 返回 0
INSERT INTO duty_schedule(duty_date, doctor_name) VALUES ('2024-06-01', 'Alice');
COMMIT;

-- 事务 B,与事务 A 并发执行
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT COUNT(*) FROM duty_schedule WHERE duty_date = '2024-06-01';
-- 返回 0
INSERT INTO duty_schedule(duty_date, doctor_name) VALUES ('2024-06-01', 'Bob');
COMMIT;

两个事务都能正常提交,最终该日期出现了两条值班记录,业务约束被破坏。可重复读的MVCC快照只保证读取的一致性,不检查读取和写入之间的因果依赖,因此无法发现这种异常。

三、可序列化的SSI冲突检测机制

PostgreSQL的可序列化级别并没有使用传统的两阶段锁或严格串行执行,而是采用SSI技术。它在可重复读快照的基础上,跟踪事务之间因读写操作形成的依赖关系,构建一个称为危险结构的图。一旦检测到可能形成环,就会选择回滚其中一个事务,以消除不可串行化的调度。

SSI的核心是识别读写反依赖。例如事务A先读取了某些行,事务B随后修改了这些行,这就形成了一种依赖。如果这种依赖反过来又影响事务A的写入,就可能构成环。可序列化级别会在提交阶段检查这些依赖,如果确定存在风险,则抛出序列化失败错误。

把上面的值班表场景切换到可序列化级别,两个事务仍然执行相同的SQL,但不会同时提交。其中一个事务会收到类似下面的错误信息。

ERROR:  could not serialize access due to read/write dependencies among transactions
DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
HINT:  The transaction might succeed if retried.

出现这个错误意味着PostgreSQL检测到了不可串行化的依赖结构,主动牺牲了一个事务的执行,保证剩余事务的调度结果等价于某个串行顺序。应用层通常需要捕获这个错误并重试事务,因为重试时快照会重新建立,能够看到之前并发事务提交的数据。

四、实际场景中的性能与选择权衡

可重复读和可序列化在性能开销上存在明显差异。可重复读基本只增加了快照维护成本,冲突检测相对较轻。可序列化因为需要跟踪读写依赖,会在内存中维护额外的结构,并在提交时进行更复杂的检查。高并发写入场景下,可序列化的中止率可能显著升高,事务重试成本不容忽视。

但这并不意味着可序列化总是不划算。对于资金转账、库存扣减、排班约束等业务规则,可序列化能够在数据库层提供完整的并发安全保证,避免应用层花费大量精力处理竞态。可重复读则更适合只读报表、数据分析、批量导出等不需要维护跨行业务约束的场景。

选择时可以先评估业务约束的粒度。如果约束可以通过唯一索引、外键等数据库约束直接表达,可重复读配合约束往往已经足够。如果约束需要跨行判断或依赖应用逻辑,例如总数不能超过阈值、同一时间不能有两个负责人,那么可序列化能省去大量手工加锁的麻烦。通过连接参数或事务语句设置隔离级别即可,例如在psql中执行 BEGIN ISOLATION LEVEL SERIALIZABLE;。

PostgreSQL可重复读可序列化修改时间:2026-09-18 01:22:06

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0918/58615.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。