PostgreSQL之MVCC多版本标记字段

xiaoxiao2021-02-28  30

作者:瀚高PG实验室 (Highgo PG Lab)-波罗

 

Postgresql表中的每一行数据(称为一个tuple),包含有4个隐藏字段,但可直接访问,它们分别是:

xmin 在创建(insert)记录(tuple)时,记录此值为插入tuple的事务ID

xmax 默认值为0,在删除tuple时,记录此值

cmin和cmax 标识在同一个事务中多个语句命令的序列值,从0开始,用于同一个事务中实现版本可见性判断

 

下面通过实验具体看看这些标记如何工作。在此之前,先创建测试表

 

CREATE TABLE test

(

  id INTEGER,

  value TEXT

);

 

Session1:

开启一个事务,查询当前事务ID(值为1855),并插入一条数据,xmin为1855,与当前事务ID相等。

符合上文所述——插入tuple时记录xmin,记录未被删除时xmax为0

postgres=> BEGIN;

BEGIN

 

postgres=> SELECT TXID_CURRENT();

 txid_current

--------------

         1855

(1 row)

 

postgres=> INSERT INTO test VALUES(1, 'a');

INSERT 0 1

 

postgres=> SELECT *, xmin, xmax, cmin, cmax FROM test;

 id | value | xmin | xmax | cmin | cmax

----+-------+------+------+------+------

  1 | a     | 1855|    0 |    0 |    0

(

(1 row)

 

继续通过一条语句插入2条记录,xmin仍然为当前事务ID,即1855,xmax仍然为0,同时cmin和cmax为1,符合上文所述cmin/cmax在事务内随着所执行的语句递增。虽然此步骤插入了两条数据,但因为是在同一条语句中插入,故其cmin/cmax都为1,在上一条语句的基础上加一。

 

INSERT INTO test VALUES(2, 'b'), (3, 'c');

INSERT 0 2

 

postgres=> SELECT *, xmin, xmax, cmin, cmax FROM test;

 id | value | xmin | xmax | cmin | cmax

----+-------+------+------+------+------

  1 | a     | 1855|    0 |    0 |    0

  2 | b     | 1855|    0 |    1 |    1

  3 | c     | 1855|    0 |    1 |    1

(

(3 rows)

 

将id为1的记录的value字段更新为'd',其xmin和xmax均未变,而cmin和cmax变为2。此时提交事务。

UPDATE test SET value = 'd' WHERE id = 1;

UPDATE 1

postgres=> SELECT *, xmin, xmax, cmin, cmax FROM test;

 id | value | xmin | xmax | cmin | cmax

----+-------+------+------+------+------

  2 | b     | 1855|    0 |    1 |    1

  3 | c     | 1855|    0 |    1 |    1

  1 | d     | 1855|    0 |    2 |    2

(3 rows)

 

postgres=> COMMIT;

COMMIT

 

开启一个新事务,通过2条语句分别插入2条id为4和5的tuple

BEGIN;

BEGIN

 

postgres=> INSERT INTO test VALUES (4, 'x');

INSERT 0 1

postgres=> INSERT INTO test VALUES (5, 'y');

INSERT 0 1

 

postgres=> SELECT *, xmin, xmax, cmin, cmax FROM test;

 

 id | value | xmin | xmax | cmin | cmax

----+-------+------+------+------+------

  2 | b     | 1855|    0 |    1 |    1

  3 | c     | 1855|    0 |    1 |    1

  1 | d     | 1855|    0 |    2 |    2

  4 | x     | 1856|    0 |    0 |    0

  5 | y     | 1856|    0 |    1 |    1

(5 rows)

 

此时,将id为2的tuple的value更新为'e',其对应的cmin/cmax被设置为2,且其xmin被设置为当前事务ID,即3278

UPDATE test SET value = 'e' WHERE id = 2;

UPDATE 1

postgres=> SELECT *, xmin, xmax, cmin, cmax FROM test;

 

 id | value | xmin | xmax | cmin | cmax

 

----+-------+------+------+------+------

  3 | c     | 1855|    0 |    1 |    1

  1 | d     | 1855|    0 |    2 |    2

  4 | x     | 1856|    0 |    0 |    0

  5 | y     | 1856|    0 |    1 |    1

  2 | e     | 1856|    0 |    2 |    2

 

Session2:

在另外一个窗口中开启一个事务,可以发现id为2的tuple,value值仍然为b,xin仍然为1855,但其xmax被设置为3278,而cmin和cmax均为2。

符合上文所述——若tuple被删除,则xmax被设置为删除tuple的事务的ID。

BEGIN;

BEGIN

postgres=> SELECT *, xmin, xmax, cmin, cmax FROM test;

 id | value | xmin | xmax | cmin | cmax

----+-------+------+------+------+------

  2 | b     | 1855| 1856|    2 |    2

  3 | c     | 1855|    0 |    1 |    1

  1 | d     | 1855|    0 |    2 |    2

(

(3 rows)

 

如果session1更改语句UPDATE test SET value = 'e' WHERE id = 2;没有提交,在session2中执行如下语句会处于等待状态,涉及更改同一条记录的锁等待的问题,直到session1的update语句被提交释放锁资源后,才能执行。

testdb=# UPDATE test SET value = 'f' WHERE id = 2;

 

这里有几点要注意

新旧窗口中id为2的tuple对应的value和xmin、xmax、cmin/cmax均不相同,实际上它们是该tuple的2个不同版本

 

在旧窗口中,更新之前,数据的顺序是2,3,1,4,5,更新后变为3,1,4,5,2。

因为在PostgreSQL中更新实际上是将旧tuple标记为删除,并插入更新后的新数据,所以更新后id为2的tuple从原来最前面变成了最后面

 

在新窗口中,id为2的tuple仍然如旧窗口中更新之前一样,排在最前面。

这是因为旧窗口中的事务未提交,更新对新窗口不可见,新窗口看到的仍然是旧版本的数据

 

提交旧窗口中的事务后,新旧窗口中看到数据完全一致——id为2的tuple排在了最后,xmin变为3278,xmax为0,cmin/cmax为2。

 

前文定义中,xmin是tuple创建时的事务ID,并没有提及更新的事务ID,但因为PostgreSQL的更新操作并非真正更新数据,而是将旧数据标记为删除,并插入新数据,所以“更新的事务ID”也就是“创建记录的事务ID”。

 

SELECT *, xmin, xmax, cmin, cmax FROM test;

 id | value | xmin | xmax | cmin | cmax

----+-------+------+------+------+------

  3 | c     | 1855|    0 |    1 |    1

  1 | d     | 1855|    0 |    2 |    2

  4 | x     | 1856|    0 |    0 |    0

  5 | y     | 1856|    0 |    1 |    1

  2 | e     | 1856|    0 |    2 |    2

(5 rows)

 

 

转载请注明原文地址: https://www.6miu.com/read-2500046.html

最新回复(0)