暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Postgresql快照和行可见性

原创 江湖小虾米 2024-01-22
465

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

行版本的可见性由tuple头上的xmin和xmax字段决定。在一个快照中tuple是否可见具体包括xmin被设置,xmax未被设置,或者说事务提交时间是在快照创建之前。如果事务被回滚,修改不应该被任何快照看到。
如下图所示:

事务1在快照创建时还处于活动状态,因此事务1的修改不应该被看见
事务2已经在快照创建之前提交,因此事务2的修改可以被看到
事务3开始于快照创建之后,因此不可见

snapshot3.png

快照结构
事务的开始可以通过xid确定,但是事务的结束时间没有地方记录,因此无法跟踪。只有事务的当前状态可以通过共享内存中的信息获取,ProcArray包含所有活动的会话和和它们的事务信息。
一个快照在创建时包含几个值:

  • xmin 快照的下边界,代表最早活动的事务ID。所有小于此ID的事务要么是commit的,要么是abort的。
  • xmax 快照的上边界,代表最近提交的事务ID+1。是快照创建的时刻。所有大于此ID的事务要么是在运行的,要么是不存在的。
  • xip_list 所有活动事务的ID列表。
    snapshot2.png

假设有如下图所示的事务,在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中。
如果落在快照区间的右边,代表着未来的事务或不存在的事务,行不可见。

最后修改时间:2024-01-22 19:54:54
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论