postgresql 从应用角度看快照snapshot使用,事务隔离控制不再神密

专栏内容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]
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

猜你喜欢

转载自blog.csdn.net/senllang/article/details/131339475