专栏内容:postgresql内核源码分析
个人主页:我的主页
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.
快照是事务中使用,配合事务的隔离级别,体现出不同的可见性。
快照在事务中自动获取,我们可以通过查看当前事务的快照和事务号来判断分析。为了方便演示,我们先创建一张表
postgres=> create table neworder(o_id integer primary key, o_info varchar, o_time timestamp);
CREATE TABLE
postgres=*> select txid_current();
txid_current
--------------
685103
(1 row)
查看当前事务号为685103
注意 txid_current() 每调一次这个函数,会消费一个事务号,在事务中只消费一个。
postgres=*> select pg_current_snapshot();
pg_current_snapshot
---------------------
685103:685103:
(1 row)
查看当前事务的快照,格式为xmin:xmax:运行中的xid列表。
我们看到xmin,xmax 为685103,说明当前没有运行的事务,最新分发的事务号就是685103注意 这里并没有分配正在运行的事务号,只有执行写操作后才开始分配
我们在另一个终端开启一个新事务,查看快照,并没有发现685103在正在运行的列表中
postgres=> begin;
BEGIN
postgres=*> select txid_current();
txid_current
--------------
685104
(1 row)
postgres=*> select pg_current_snapshot();
pg_current_snapshot
---------------------
685103:685103:
(1 row)
postgres=*> end;
COMMIT
- 在终端一的事务上插入一行数据
postgres=*> insert into neworder values(1,'football',now());
INSERT 0 1
这样做,让此事务有运行中的事务号
- 我们这里在第二个终端重新开启一个事务
postgres=> begin;
BEGIN
postgres=*> select txid_current();
txid_current
--------------
685105
(1 row)
postgres=*> select pg_current_snapshot();
pg_current_snapshot
----------------------
685103:685105:685103
(1 row)
我们看到当前快照中xmin为685103,正在运行的最小事务号;
xmax为685105 ,正在运行中只有一个事务
我们启动了两个默认为读已经提交的事务
- 现在在终端二上继续执行
postgres=*> create table neworder1(o1_id int);
CREATE TABLE
postgres=*> select pg_current_snapshot();
pg_current_snapshot
----------------------
685103:685105:685103
(1 row)
创建了一张表,快照没有发生变化
- 终端一上提交事务
postgres=*> commit;
COMMIT
- 在终端二事务中插入数据
postgres=*> insert into neworder values(2,'basketball',now());
INSERT 0 1
postgres=*> select pg_current_snapshot();
pg_current_snapshot
---------------------
685105:685105:
(1 row)
此时查看到的快照发生了变化,xmin为自己事务,当前运行中的事务没有了。
- 原理
读已经提交的快照,在每次命令都会获取新的快照,如果有提交的事务,那么就会变为可见
postgres=*> select * from neworder;
o_id | o_info | o_time
------+------------+----------------------------
1 | football | 2023-06-21 08:56:43.541668
2 | basketball | 2023-06-21 09:15:10.313662
(2 rows)
此时就可以看到前一事务插入的数据
- 终端一启动可重复读事务
postgres=> begin isolation level repeatable read ;
BEGIN
postgres=*> insert into neworder values(3,'delicious food',now());
INSERT 0 1
postgres=*> select txid_current();
txid_current
--------------
685107
(1 row)
postgres=*> select pg_current_snapshot();
pg_current_snapshot
---------------------
685107:685107:
(1 row)
快照中,此时没有正在运行中的事务,xmin就是自己的事务号
- 终端二启动可重复读事务
postgres=> begin isolation level repeatable read ;
BEGIN
postgres=*> insert into neworder1 values(1);
INSERT 0 1
postgres=*> select txid_current();
txid_current
--------------
685108
(1 row)
postgres=*> select pg_current_snapshot();
pg_current_snapshot
---------------------
685107:685107:
(1 row)
快照中,此时运行中的事务xmin为685107,xmax也为685107
- 终端二提交事务
postgres=*> commit;
COMMIT
- 终端一执行插入
postgres=*> insert into neworder values(4,'delicious tomato',now());
INSERT 0 1
postgres=*> select pg_current_snapshot();
pg_current_snapshot
---------------------
685107:685107:
(1 row)
事务快照并没有变化
- 原理
对于可重复读,每个事务只获取一次快照,这样可见性在事务开始时就已经确定
在使用对应的表后,对于读写会产生冲突,导致阻塞;
如果没有使用要修改的表,则不会产生冲突。
如果事务没有操作过该表,在另一事务修改提交,就可以看到修改;
先启动事务1,操作表neworder1,然后在事务2操作neworder的表定义,提交事务2后,在事务1进行查看。
事务1
postgres=> begin;
BEGIN
postgres=*> insert into neworder1 values(5);
INSERT 0 1
事务2 修改数据字典
postgres=> begin;
BEGIN
postgres=*>
postgres=*> alter table neworder drop column o_time;
ALTER TABLE
postgres=*> commit;
COMMIT
事务1 查询数据字典的同步,可以看到neworder已经没有了o_time列
postgres=*> select * from neworder;
o_id | o_info
------+------------------
1 | football
2 | basketball
3 | delicious food
4 | delicious tomato
10 | potato
(5 rows)
postgres=*> commit;
COMMIT
和上面顺序一样,先启动事务1,操作表neworder1,然后在事务2操作neworder的表定义,提交事务2后,在事务1进行查看。
事务1
postgres=> begin;
BEGIN
postgres=*> insert into neworder1 values(5);
INSERT 0 1
事务2 修改数据字典,增加列o_time
postgres=> begin isolation level repeatable read ;
BEGIN
postgres=*> alter table neworder add column o_time timestamp;
ALTER TABLE
postgres=*> commit;
COMMIT
事务1 查询数据字典的同步
postgres=*> select * from neworder;
o_id | o_info | o_time
------+------------------+--------
1 | football |
2 | basketball |
3 | delicious food |
4 | delicious tomato |
10 | potato |
(5 rows)
postgres=*> select pg_current_snapshot();
pg_current_snapshot
---------------------
685125:685125:
(1 row)
postgres=*> select txid_current();
txid_current
--------------
685125
(1 row)
postgres=*>
与预期不一样的事情发了 这里在可重复读时,系统字典的更新可以被看到了。
这一点在源码分析时,没有被关注到。
快照还可以保存,被重复使用,有点像科幻中保存记忆一样。
下面我们来看看如何使用这一特异功能。
开启事务1,保存事务1的快照,然后在别一个事务中应用此快照,那么这两个事务看到的内容是一样的。
事务1 启动可重复读事务,并导出快照
postgres=> begin isolation level repeatable read ;
BEGIN
postgres=*> insert into neworder1 values(5);
INSERT 0 1
postgres=*> select pg_current_snapshot();
pg_current_snapshot
---------------------
685125:685125:
(1 row)
postgres=*> select txid_current();
txid_current
--------------
685125
(1 row)
postgres=*> select * from neworder;
o_id | o_info | o_time
------+------------------+--------
1 | football |
2 | basketball |
3 | delicious food |
4 | delicious tomato |
10 | potato |
(5 rows)
postgres=*> SELECT pg_export_snapshot();
pg_export_snapshot
---------------------
00000004-00000058-1
(1 row)
旧快照能被使用的前提是,生成旧快照必须有事务在使用,否则快照就是一个过期快照
事务2 在启动事务2前,向neworder表中插入数据,然后启动事务2,在其中使用保存的快照
postgres=> insert into neworder values(100,'test dic',now());
INSERT 0 1
postgres=> insert into neworder values(101,'erase',now());
INSERT 0 1
postgres=> select * from neworder;
o_id | o_info | o_time
------+------------------+----------------------------
1 | football |
2 | basketball |
3 | delicious food |
4 | delicious tomato |
10 | potato |
100 | test dic | 2023-06-22 10:50:35.238614
101 | erase | 2023-06-22 10:55:12.268132
(7 rows)
postgres=> BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN
postgres=*> set transaction snapshot '00000004-00000058-1';
SET
postgres=*> select * from neworder;
o_id | o_info | o_time
------+------------------+--------
1 | football |
2 | basketball |
3 | delicious food |
4 | delicious tomato |
10 | potato |
(5 rows)
postgres=*> select pg_current_snapshot();
pg_current_snapshot
---------------------
685125:685125:
(1 row)
非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!
作者邮箱:[email protected]
如有错误或者疏漏欢迎指出,互相学习。
注:未经同意,不得转载!