PostgreSQL中的事务隔离与导出快照
内容提要
PostgreSQL 的隔离级别从宽松到严格依次为读已提交、可重复读、可串行化,分别防止脏读、不可重复读、幻读和序列化异常。MVCC 通过快照实现隔离,快照记录事务状态而非 LSN。CDC 中,复制槽创建时导出快照,配合 WAL 流确保数据完整,需用可重复读只读事务导入快照。
延伸解读
快照与LSN的分工
文章强调,PostgreSQL的导出快照不是WAL中的LSN位置,而是记录事务提交状态的内存结构。LSN只能定位变更流中的位置,无法反映哪些事务已提交、哪些仍在进行。因此,CDC初始导出必须同时使用快照名(通过SET TRANSACTION SNAPSHOT读取初始状态)和LSN(通过START_REPLICATION流式获取后续变更),两者互补,缺一不可。
为何必须用可重复读只读事务
导入导出快照时,事务必须使用REPEATABLE READ,因为该级别在第一条语句时固定快照,而READ COMMITTED每条语句都会获取新快照,无法保持外部快照的可见性。SERIALIZABLE虽也可用,但会额外跟踪读写依赖并可能中止事务,对只读导出而言是多余开销。READ ONLY则明确意图,避免意外写入,并允许优化器减少锁和WAL开销。
快照的生命周期与CDC实践
导出快照仅在复制连接保持打开且未执行新命令时有效。CDC系统通常使用两个连接:复制连接创建槽并持有快照,普通SQL连接导入快照读取全量数据,完成后提交,复制连接再启动WAL流。这种模式确保快照与WAL流无缝衔接,无间隙无重叠。注意,pg_create_logical_replication_slot()函数不返回快照名,必须通过复制协议创建槽才能获得。
Q&A
PostgreSQL 支持哪几种事务隔离级别?它们分别防止哪些读异常?
PostgreSQL 支持读已提交(默认)、可重复读和可串行化三种隔离级别(读未提交与读已提交行为相同)。读已提交防止脏读,可重复读防止脏读、不可重复读和幻读,可串行化防止脏读、不可重复读、幻读和序列化异常。
PostgreSQL 中 READ COMMITTED 和 REPEATABLE READ 在快照使用上有什么区别?
READ COMMITTED 下每个语句都会获取一个新的快照,因此同一事务中两次相同查询可能返回不同结果;REPEATABLE READ 在事务的第一个语句时获取快照,整个事务期间都使用该快照,因此后续查询看到的数据保持一致。
什么是脏读、不可重复读、幻读和序列化异常?
脏读是读到其他事务未提交的数据;不可重复读是同一事务中两次读取同一行得到不同值;幻读是同一事务中两次查询返回不同的行集合;序列化异常是并发事务的结果无法与任何串行执行顺序等价。
PostgreSQL 的 MVCC 快照包含哪些信息?为什么快照名称不是 LSN?
MVCC 快照包含最高已提交事务 ID、进行中事务 ID 列表和最早活动事务 ID。快照名称是完整可见性状态的句柄,而 LSN 只是 WAL 中的字节偏移,无法反映哪些事务已提交或仍在进行,因此不能替代快照。
在 CDC 中,如何使用导出快照和复制槽来保证数据一致性?
创建复制槽时,PostgreSQL 会导出一个快照,该快照反映了槽创建时刻的数据库状态。CDC 系统通过复制连接创建槽并保持快照有效,同时用普通 SQL 连接以 REPEATABLE READ READ ONLY 事务导入快照并读取全量数据,之后复制连接再开始 WAL 流,从而覆盖从快照点到后续变更的完整时间线,无间隙无重叠。
为什么导出快照的事务必须使用 REPEATABLE READ READ ONLY?
SET TRANSACTION SNAPSHOT 要求事务隔离级别为 REPEATABLE READ,因为 READ COMMITTED 下每个语句都会获取新快照,无法固定到外部快照;SERIALIZABLE 虽然可行但会带来额外开销。READ ONLY 防止意外写入,并允许 PostgreSQL 优化事务,因为导出过程只读。
如何通过复制协议创建复制槽并获取快照名称?
使用复制协议连接(连接串包含 replication=database),执行 CREATE_REPLICATION_SLOT 命令,返回结果中包含 slot_name、consistent_point、snapshot_name 和 output_plugin。快照名称用于后续 SET TRANSACTION SNAPSHOT。