在上一文PostgreSQL并发控制(一)中。我们解释了PostgreSQL多版本并发控制MVCC
(Multi-version Concurrency Control)是在写数据时创建一个新版本数据,并将新旧版本数据都保存在数据文件中。而并行的其他事务查询数据时,应该选择哪一个版本的数据来呈现呢?本文将进行讲解。
一、事务快照
事务快照是在某个时间点看到的事务状态信息,包括哪些事务已经完成,那些事务还未开始,哪些事务正在进行中。在PostgreSQL中,用txid_current_snapshot()
函数来获得当前的事务快照。
postgres=# select txid_current_snapshot(); txid_current_snapshot ----------------------- 678:678: (1 row)
事务快照的文本表现形式:xmin:xmax:xip_list
具体含义如下:
事务快照项 | 解释 | 说明 |
---|---|---|
xmin | Earliest transaction ID (txid) that is still active. All earlier transactions will either be committed and visible, or rolled back and dead. | 最早的活跃事务的txid,txid<xmin的事务要么提交,要么回滚 |
xmax | First as-yet-unassigned txid. All txids greater than or equal to this are not yet started as of the time of the snapshot, and thus invisible. | 第一个尚未分配的txid,txid>=xmin的事务没有开启,所以不可见。 |
xip_list | Active txids at the time of the snapshot. The list includes only those active txids between xmin and xmax ; there might be active txids higher than xmax . A txid that is xmin <= txid < xmax and not in this list was already completed at the time of the snapshot, and thus either visible or dead according to its commit status. The list does not include txids of subtransactions. | xmin<=txid<xmax中仍然活跃的事务。 |
例1: 100:100:
。 xmin
为100,因此txid < 100
的事务是非活跃的,要么提交,要么回滚。xmax
为100,因此txid ≥ 100
的事务尚未开始。
例2: 100:104:100,102
。xmin
为100,因此txid < 100
的事务是非活跃的,要么提交,要么回滚。xmax
为104,因此txid ≥ 104
的事务尚未开始。xip_list
为100,102,所以100,102号事务仍然活跃,101,103号事务不活跃,要么提交、要么回滚。
非活跃事务是回滚还是提交?需要查看PostgreSQL中clog记录,本文不做解释。
二、事务快照与事务隔离级别
PostgreSQL中事务隔离级别是通过事务快照来实现的,获取事务快照用来检查元组的可见性。对read committed
隔离级别,每执行一条语句都会获得事务快照;对repeatable read
,事务只会在执行第一条SQL时获取一次快照。注意,事务快照不是在事务开始时获取,而是在事务中执行语句时获取,看例子:
时间 | 事务一 | 事务二 | 事务三 | 说明 |
---|---|---|---|---|
Time_1 | begin transaction isolation level repeatable read; | 事务二首先开始,并设置为repeatable read,此时事务二并没有获取事务快照。 | ||
Time_2 | begin; | 事务一开始,默认read committed隔离级别 | ||
Time_3 | select count(1) from a; 结果是0条 insert into a values (1 , 'a'); | 事务一插入一条记录。 | ||
Time_4 | commit; | 事务一提交 | ||
Time_5 | select count(1) from a; 结果是1条 | 事务二获取事务快照,此时事务一已经提交,事务一的结果可以查询 | ||
Time_6 | begin; insert into a values (2 , 'b'); commit ; | 事务三又插入一条记录,默认read committed隔离级别。 | ||
Time_7 | select count(1) from a; 结果是1条 | 事务二仍然使用Time_5获取是事务快照,只看到一条记录。 | ||
Time_8 | commit; | 事务二结束 |