每个页中相同的行可能有多个版本,每个事务只能看到其中的一个版本,为保证隔离性,每个事务都有自己的快照版本。rc隔离级别下,快照开始于每个语句。rr隔离级别下,快照开始于事务的第一个语句,直到事务结束。

行版本的可见性由tuple头上的xmin和xmax字段决定。在一个快照中tuple是否可见具体包括xmin被设置,xmax未被设置,或者说事务提交时间是在快照创建之前。如果事务被回滚,修改不应该被任何快照看到。
如下图所示:
事务1在快照创建时还处于活动状态,因此事务1的修改不应该被看见
事务2已经在快照创建之前提交,因此事务2的修改可以被看到
事务3开始于快照创建之后,因此不可见

快照结构
事务的开始可以通过xid确定,但是事务的结束时间没有地方记录,因此无法跟踪。只有事务的当前状态可以通过共享内存中的信息获取,ProcArray包含所有活动的会话和和它们的事务信息。
一个快照在创建时包含几个值:
- xmin 快照的下边界,代表最早活动的事务ID。所有小于此ID的事务要么是commit的,要么是abort的。
- xmax 快照的上边界,代表最近提交的事务ID+1。是快照创建的时刻。所有大于此ID的事务要么是在运行的,要么是不存在的。
- xip_list 所有活动事务的ID列表。

假设有如下图所示的事务,在t6时刻,事务3的查询能看到几条数据:
事务1 事务2 事务3 事务4 t1 begin; insert into p values('myq'); t2 begin; insert into p values('kitty'); commit; t3 BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT pg_current_snapshot(); t4 commit; t5 begin; insert into p values('alice'); commit; t6 select * from p;
操作结果如下:
#t1. 第一个事务插入一条数据不提交
mydb=# begin;
BEGIN
mydb=*# insert into p values('myq');
INSERT 0 1
mydb=*# SELECT pg_current_xact_id();
pg_current_xact_id
--------------------
811
(1 row)
#t2. 第二个事务插入一条数据并及时提交
mydb=# begin;
BEGIN
mydb=*# insert into p values('kitty');
INSERT 0 1
mydb=*# SELECT pg_current_xact_id();
pg_current_xact_id
--------------------
812
(1 row)
mydb=*# commit;
COMMIT
#t3. 在另一个会话中创建一个快照
mydb=# begin isolation level repeatable read;
BEGIN
mydb=*# SELECT pg_current_snapshot();
pg_current_snapshot
---------------------
811:813:811
(1 row)
mydb=*# SELECT pg_current_xact_id();
pg_current_xact_id
--------------------
813
(1 row)
#t4. 提交第一个事务
mydb=*# commit;
COMMIT
#t5. 执行第3个事务
mydb=# begin;
BEGIN
mydb=*# insert into p values('alice');
INSERT 0 1
mydb=*# SELECT pg_current_xact_id();
pg_current_xact_id
--------------------
814
(1 row)
mydb=*# commit;
COMMIT
#t6. 在快照会话中查询表中的数据,仅能看到事务2插入的数据。
mydb=*# select * from p;
name
-------
kitty
(1 row)
#表中实际的数据
mydb=# SELECT * FROM heap_page('accounts',0);
ctid | state | xmin | xmax
-------+--------+-------+------
(0,1) | normal | 747 | 0 a
(0,2) | normal | 748 c | 749
(0,3) | normal | 749 | 0 a
(3 rows)
步骤1开启一个事务不提交
步骤2开启一个事务并及时提交
步骤3创建一个快照
步骤4提交第一个事务
步骤5执行第3个事务并及时提交
步骤6在快照视图中查询表的数据
由于在创建快照时事务1还处于活动状态,因此事务1的修改不可见。事务2在快照创建时已提交,因此事务2的修改可见。
事务3是在快照创建后开启的,因此事务3的修改也不可见。所以在快照视图中只能查询到事务2操作的一条数据。
总结:
行可见性规则大致如下。
RC隔离级别下,事务快起起始于每一个语句,RR隔离级别下快照起始于第一个语句,直到事务结束。
行对事务是否可见取决于行上的事务落在快照的哪个区间
如果落在快照区间的左边,表时快照创建时事务已经完成。此时就要判断行上的xmax是否置位,xmax置位了该行不可见。xmax没置位,就要判断xmin的状态,如果是提交状态,行可见。如果是abort,行不可见。
如果落在快照区间的中间,修改是否可见取决于相应的事务ID是否在xip_list中。
如果落在快照区间的右边,代表着未来的事务或不存在的事务,行不可见。




