PHP实战中知识总结 / PgSQL - 事务快照实例
pg的事务快照功能在9.2版本开始支持,允许事务共享它当时的snapshot给其他的事务使用。
SET TRANSACTION SNAPSHOT 命令允许新的事务使用与一个现有事务相同的快照运行。已经存 在的事务必须已经把它的快照用pg_export_snapshot函数导出。该函数会返回一个快照标识符,SET TRANSACTION SNAPSHOT需要被给定一个快照标识符来指定要导入的快照。在这个命令中该标识符必须被写成一个字符串,例如'000003A1-1'。SET TRANSACTION SNAPSHOT只能在一个事务的 开始执行,并且要在该事务的第一个查询或者数据修改语句(SELECT、INSERT、DELETE、UPDATE、FETCH或COPY)之前执行。此外,该事务必须已经被设置 为SERIALIZABLE或者REPEATABLE READ隔离级别(否则,该快照将被立刻抛弃, 因为READ COMMITTED模式会为每一个命令取一个新快照)。 如果导入事务使用了SERIALIZABLE隔离级别,那么导入快照 的事务必须也使用该隔离级别。还有,一个非只读可序列化事务不能导入来自只读事务的快照。
一、实例1:export的事务是repeatable read 隔离级别
1、session 1开启repeatable read隔离级别事务,并使用pg_export_snapshot函数导出快照标识。
postgres=# begin TRANSACTION ISOLATION LEVEL repeatable read;
BEGIN
postgres=*# SELECT pg_export_snapshot();
pg_export_snapshot
---------------------
00000004-0000003C-1
(1 row)
postgres=*# select * from tbl;
data
------
C
B
(2 rows)
2、session 2 新增数据并commit。
[postgres@iZbp1bum6107bp8mgzkeunZ ~]$ psql
psql (13.0)
Type "help" for help.
postgres=# INSERT INTO tbl VALUES('A');
INSERT 0 1
postgres=# select * from tbl;
data
------
C
B
A
(3 rows)
3、session 3 :默认的read committed的隔离级别下,能看到session 2提交的修改 ,但是在新事务中,通过SET TRANSACTION SNAPSHOTsession 1中export snapshot的事务后,看不到session 2中新增的数据。
// 默认的read committed的隔离级别下,能看到session 2提交的修改
postgres=# select * from tbl;
data
------
C
B
A
(3 rows)
// 导入 session 1 导出的快照,发现导入失败,因为快照导入事务必须具有可序列化或可重复读取的隔离级别
postgres=# SET TRANSACTION SNAPSHOT '00000004-0000003C-1';
WARNING: SET TRANSACTION can only be used in transaction blocks
ERROR: a snapshot-importing transaction must have isolation level SERIALIZABLE or REPEATABLE READ
// 设置事务隔离级别为repeatable read
postgres=# BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN
// 导入session 1的快照之后,发现看不到session 2的数据
postgres=*# SET TRANSACTION SNAPSHOT '00000004-0000003C-1';
SET
postgres=*# select * from tbl;
data
------
C
B
(2 rows)
在session 1 导出快照之后,会在$PGDATA/pg_snapshots目录下生成与snapshot同名的文件,该文件就是快照文件。事务commit之后,文件就会被删除。
[root@iZbp1bum6107bp8mgzkeunZ pg_snapshots]# l
00000004-0000003C-1
[root@iZbp1bum6107bp8mgzkeunZ pg_snapshots]# cat 00000004-0000003C-1
vxid:4/60
pid:17797
dbid:13580
iso:2
ro:0
xmin:575
xmax:575
xcnt:0
sof:0
sxcnt:0
rec:0
4、结论:
session 1 export snapshot。session 2 通过SET TRANSACTION SNAPSHOT导入session 1 的snapshot。这样不管session 1 export snapshot后有没有别的session 提交事务,都不影响session 2。
二、实例2:export的事务是read committed隔离级别。
1、session 1开启read committed隔离级别的事务,插入数据并导出快照。
// 事务开始前,插入一条数据
postgres=# insert into tbl values ('A');
INSERT 0 1
// 开启read committed隔离级别的事务
postgres=# begin TRANSACTION ISOLATION LEVEL read committed;
BEGIN
postgres=*# SELECT pg_export_snapshot();
pg_export_snapshot
---------------------
00000004-00000048-1
(1 row)
postgres=*# select * from tbl;
data
------
A
(1 row)
postgres=*# insert into tbl values ('B');
INSERT 0 1
postgres=*# SELECT pg_export_snapshot();
pg_export_snapshot
---------------------
00000004-00000048-2
(1 row)
postgres=*# select * from tbl;
data
------
A
B
(2 rows)
2、session 2中查看,只能看到A数据,不能看到B数据,因为B数据在session 1中的事务中还没提交。
postgres=# select * from tbl;
data
------
A
(1 row)
3、session 3 中新增一条数据并commit,session 1的事务中可以看到数据。
// session 3 插入一条数据
postgres=# INSERT INTO tbl VALUES('C');
INSERT 0 1
// session 1中可以看到这条数据
postgres=*# select * from tbl;
data
------
A
B
C
(3 rows)
postgres=*# SELECT pg_export_snapshot();
pg_export_snapshot
---------------------
00000004-00000048-3
4、session 2 中导入快照00000004-00000048-1,则只能查到数据A。
postgres=# begin TRANSACTION ISOLATION LEVEL repeatable read;
BEGIN
postgres=*# SET TRANSACTION SNAPSHOT '00000004-00000048-1';
SET
postgres=*# select * from tbl;
data
------
A
(1 row)
5、session 3 中导入快照00000004-00000048-2,也只能查到数据A。
postgres=# begin TRANSACTION ISOLATION LEVEL repeatable read;
BEGIN
postgres=*# SET TRANSACTION SNAPSHOT '00000004-00000048-2';
SET
postgres=*# select * from tbl;
data
------
A
(1 row)
6、session 4 中导入快照00000004-00000048-3,则能查看到A、C的数据。
postgres=# begin TRANSACTION ISOLATION LEVEL repeatable read;
BEGIN
postgres=*# SET TRANSACTION SNAPSHOT '00000004-00000048-3';
SET
postgres=*# select * from tbl;
data
------
A
C
(2 rows)
postgres=*# commit;
COMMIT
7、session 5 insert一条新的数据D。
// session 5 新增一条数据,只查询到A、C、D,因为B在session 1中的事务中未提交
postgres=# insert into tbl values ('D');
INSERT 0 1
postgres=# select * from tbl;
data
------
A
C
D
(3 rows)
// session 2和session 3、session 4的查询结果跟前面一致
// 而session 1中可以看到D
postgres=*# select * from tbl;
data
------
A
B
C
D
(4 rows)
8、session 1执行commit,其他事务中依然看不到其他数据。
9、结论
repeatable read和serializable是看不到事务中其他事务提交的数据的, read committed则可以看到。
三、总结
import事务快照时,其实只是把执行export事务的当时的txid_current_snapshot传递过来. 不会传递事务的隔离属性如(read committed或repeatable read或serializable)。
共享事务snapshot的事务之间,除了存在自己修改的数据的差异之外,对于执行export的事务如果是read committed的,它看到的数据和执行import的事务看到的数据也是存在差异的。