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

许多资料会把可重复读等同于防止不可重复读,把可序列化等同于防止幻读。但在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