PostgreSQL DBA(21) - MVCC#1(Multi Version Heap Tuple)

Return to ITPUB blog

Concurrency Control并发控制是一种机制,在并发进行多个事务时维护一致性(Consistency)和隔离性(Isolation),一致性和隔离性是数据库事务ACID(Atomicity, Consistency, Isolation, Durability) 属性中的C和I。
多版本并发控制(MVCC)是广泛使用的并发控制技术,其主要优势是读不会阻塞写,而写也不会阻塞读。MVCC有很多种变体,PostgreSQL使用一种称为快照隔离Snapshot Isolation (SI)的MVCC变体实现并发控制。
在MVCC中,每个DML操作创建一个数据(包括Index)的新版本,同时保留之前的旧版本。当事务读取数据时,选择其中一个“正确”的版本,以确保各个事务之间的隔离。
下面按DML的类型(INSERT/UPDATE/DELETE)简单介绍PostgreSQL中数据(Heap Tuple)的多版本存储结构。

零、隐藏列和infomask标记

为了更好的说明Heap Tuple的存储结构,有必要先简要说明Tuple的隐藏列以及相关的标记.
隐藏列

testdb=# select attname, attnum, atttypid::regtype, attisdropped::text from pg_attribute where attrelid=34374;
 attname  | attnum |     atttypid      | attisdropped 
----------+--------+-------------------+--------------
 tableoid |     -7 | oid               | false
 cmax     |     -6 | cid               | false
 xmax     |     -5 | xid               | false
 cmin     |     -4 | cid               | false
 xmin     |     -3 | xid               | false
 ctid     |     -1 | tid               | false
 c1       |      1 | integer           | false
 c2       |      2 | character varying | false
 c3       |      3 | character varying | false
(9 rows)

tableoid-数据表OID
cmax-删除该tuple的事务内部命令ID
xmax-删除该tuple的事务ID
cmin-插入该tuple的事务内部命令ID
xmin-插入该tuple的事务ID
ctid-heap tuple的ID

infomask标记
主要的标记包括t_infomask2和t_infomask.
t_infomask2
取值和说明如下

/*
 * information stored in t_infomask2:
 */
#define HEAP_NATTS_MASK 0x07FF /* 11 bits for number of attributes */
//低11位为属性个数
/* bits 0x1800 are available */
#define HEAP_KEYS_UPDATED 0x2000 /* tuple was updated and key cols
 * modified, or tuple deleted */
#define HEAP_HOT_UPDATED 0x4000 /* tuple was HOT-updated */
#define HEAP_ONLY_TUPLE 0x8000 /* this is heap-only tuple */
#define HEAP2_XACT_MASK 0xE000 /* visibility-related bits */
//把十六进制值转换为二进制显示
     11111111111 #define HEAP_NATTS_MASK         0x07FF 
  10000000000000 #define HEAP_KEYS_UPDATED       0x2000  
 100000000000000 #define HEAP_HOT_UPDATED        0x4000  
1000000000000000 #define HEAP_ONLY_TUPLE         0x8000  
1110000000000000 #define HEAP2_XACT_MASK         0xE000 
1111111111111110 #define SpecTokenOffsetNumber       0xfffe

t_infomask
取值和说明如下

//t_infomask说明
               1 #define HEAP_HASNULL            0x0001  /* has null attribute(s) */
              10 #define HEAP_HASVARWIDTH        0x0002  /* has variable-width attribute(s) */
             100 #define HEAP_HASEXTERNAL        0x0004  /* has external stored attribute(s) */
            1000 #define HEAP_HASOID             0x0008  /* has an object-id field */
           10000 #define HEAP_XMAX_KEYSHR_LOCK   0x0010  /* xmax is a key-shared locker */
          100000 #define HEAP_COMBOCID           0x0020  /* t_cid is a combo cid */
         1000000 #define HEAP_XMAX_EXCL_LOCK     0x0040  /* xmax is exclusive locker */
        10000000 #define HEAP_XMAX_LOCK_ONLY     0x0080  /* xmax, if valid, is only a locker */
                    /* xmax is a shared locker */
                 #define HEAP_XMAX_SHR_LOCK  (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)
                 #define HEAP_LOCK_MASK  (HEAP_XMAX_SHR_LOCK | HEAP_XMAX_EXCL_LOCK | \
                          HEAP_XMAX_KEYSHR_LOCK)
       100000000 #define HEAP_XMIN_COMMITTED     0x0100  /* t_xmin committed */
      1000000000 #define HEAP_XMIN_INVALID       0x0200  /* t_xmin invalid/aborted */
                 #define HEAP_XMIN_FROZEN        (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)
     10000000000 #define HEAP_XMAX_COMMITTED     0x0400  /* t_xmax committed */
    100000000000 #define HEAP_XMAX_INVALID       0x0800  /* t_xmax invalid/aborted */
   1000000000000 #define HEAP_XMAX_IS_MULTI      0x1000  /* t_xmax is a MultiXactId */
  10000000000000 #define HEAP_UPDATED            0x2000  /* this is UPDATEd version of row */
 100000000000000 #define HEAP_MOVED_OFF          0x4000  /* moved to another place by pre-9.0
                                                          * VACUUM FULL; kept for binary
                                                          * upgrade support */
1000000000000000 #define HEAP_MOVED_IN           0x8000  /* moved from another place by pre-9.0
                                                          * VACUUM FULL; kept for binary
                                                          * upgrade support */
                 #define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)
1111111111110000 #define HEAP_XACT_MASK          0xFFF0  /* visibility-related bits */

一、INSERT

创建数据表,插入数据

testdb=# drop table if exists t_mvcc1;
DROP TABLE
testdb=# create table t_mvcc1 (c1 int,c2 varchar(40));
CREATE TABLE
testdb=# 
testdb=# insert into t_mvcc1 values(1,'C2-1');
INSERT 0 1
testdb=# insert into t_mvcc1 values(2,'C2-2');
INSERT 0 1
testdb=# 

通过pageinspect插件查看page中的内容


testdb=# select lp,lp_off,lp_flags,t_xmin,t_xmax,t_field3 as t_cid,t_ctid,t_infomask2,t_infomask from heap_page_items(get_raw_page('t_mvcc1',0));
 lp | lp_off | lp_flags | t_xmin | t_xmax | t_cid | t_ctid | t_infomask2 | t_infomask 
----+--------+----------+--------+--------+-------+--------+-------------+------------
  1 |   8152 |        1 |   2300 |      0 |     0 | (0,1)  |           2 |       2050
  2 |   8112 |        1 |   2301 |      0 |     0 | (0,2)  |           2 |       2050
(2 rows)

其中lp为Line Pointer(ItemID,行指针),t_xmin(分别是2300&2301)为插入数据的事务ID,t_xmax为0(Invalid事务号),t_cid是命令编号,t_ctid是heap tuple ID,详细解释请参见参考资料.
t_infomask2为0x0002,说明有2个字段(低11位为属性的个数);
t_infomask为2050,即0x0802,标记存在可变长属性(HEAP_HASVARWIDTH)/XMAX无效(HEAP_XMAX_INVALID)

二、UPDATE

更新数据(提交事务)

testdb=# 
testdb=# begin;
BEGIN
testdb=# 
testdb=# update t_mvcc1 set c2='C2#1' where c1 = 1;
UPDATE 1
testdb=# update t_mvcc1 set c2='C2#2' where c1 = 2;
UPDATE 1
testdb=# 
testdb=# commit;
COMMIT

通过pageinspect插件查看page中的内容

testdb=# select lp,lp_off,lp_flags,t_xmin,t_xmax,t_field3 as t_cid,t_ctid,t_infomask2,t_infomask from heap_page_items(get_raw_page('t_mvcc1',0));
 lp | lp_off | lp_flags | t_xmin | t_xmax | t_cid | t_ctid | t_infomask2 | t_infomask 
----+--------+----------+--------+--------+-------+--------+-------------+------------
  1 |   8152 |        1 |   2300 |   2302 |     0 | (0,3)  |       16386 |        258
  2 |   8112 |        1 |   2301 |   2302 |     1 | (0,4)  |       16386 |        258
  3 |   8072 |        1 |   2302 |      0 |     0 | (0,3)  |       32770 |      10242
  4 |   8032 |        1 |   2302 |      0 |     1 | (0,4)  |       32770 |      10242
(4 rows)

可以看到原数据仍存在,但t_xmax值为2302,表示这两行已被更新,同时t_ctid指向新的heap tuple.
1/2号tuple的t_infomask2是16386即0x4002 -> HEAP_HOT_UPDATED
t_infomask是258,即0x0102 -> HEAP_XMIN_COMMITTED | HEAP_HASVARWIDTH

3/4号tuple的t_infomask2是32770,即0x8002 -> HEAP_ONLY_TUPLE
t_infomask是10242,即0x2802 -> HEAP_UPDATED | HEAP_XMAX_INVALID | HEAP_HASVARWIDTH

更新数据(回滚事务)

testdb=# begin;
BEGIN
testdb=# 
testdb=# update t_mvcc1 set c2='C2_1' where c1 = 1;
UPDATE 1
testdb=# update t_mvcc1 set c2='C2_2' where c1 = 2;
UPDATE 1
testdb=# 
testdb=# rollback;
ROLLBACK
testdb=# select cmin,cmax,xmin,xmax,ctid,c1,c2 from t_mvcc1;
 cmin | cmax | xmin | xmax | ctid  | c1 |  c2  
------+------+------+------+-------+----+------
    0 |    0 | 2302 | 2303 | (0,3) |  1 | C2#1
    1 |    1 | 2302 | 2303 | (0,4) |  2 | C2#2
(2 rows)

通过pageinspect插件查看page中的内容

testdb=# select lp,lp_off,lp_flags,t_xmin,t_xmax,t_field3 as t_cid,t_ctid,t_infomask2,t_infomask from heap_page_items(get_raw_page('t_mvcc1',0));
 lp | lp_off | lp_flags | t_xmin | t_xmax | t_cid | t_ctid | t_infomask2 | t_infomask 
----+--------+----------+--------+--------+-------+--------+-------------+------------
  1 |   8152 |        1 |   2300 |   2302 |     0 | (0,3)  |       16386 |       1282
  2 |   8112 |        1 |   2301 |   2302 |     1 | (0,4)  |       16386 |       1282
  3 |   8072 |        1 |   2302 |   2303 |     0 | (0,5)  |       49154 |       8450
  4 |   8032 |        1 |   2302 |   2303 |     1 | (0,6)  |       49154 |       8450
  5 |   7992 |        1 |   2303 |      0 |     0 | (0,5)  |       32770 |      10242
  6 |   7952 |        1 |   2303 |      0 |     1 | (0,6)  |       32770 |      10242
(6 rows)

3/4号(lp=3/4)tuple被更新,t_xmax设置为更新事务的ID,但事务rollback(PG通过clog记录事务状态,clog后续再行讨论).
t_infomask2=49154,即0xC002
t_infomask=8450,即0x2102 -> HEAP_UPDATED | HEAP_XMIN_COMMITTED | HEAP_HASVARWIDTH

5/6号tuple是新生成的更新记录,但事务rollback.
t_infomask2=32770,即0x8002 -> HEAP_ONLY_TUPLE
t_infomask=10242,即0x2802 -> HEAP_UPDATED | HEAP_XMAX_INVALID | HEAP_HASVARWIDTH

三、DELETE

删除数据(提交事务)

testdb=# begin;
BEGIN
testdb=# 
testdb=# delete from t_mvcc1 where c1 = 1;
DELETE 1
testdb=# 
testdb=# commit;
COMMIT
testdb=# 
testdb=#  select cmin,cmax,xmin,xmax,ctid,c1,c2 from t_mvcc1;
 cmin | cmax | xmin | xmax | ctid  | c1 |  c2  
------+------+------+------+-------+----+------
    1 |    1 | 2302 | 2303 | (0,4) |  2 | C2#2
(1 row)

通过pageinspect插件查看page中的内容

testdb=# select lp,lp_off,lp_flags,t_xmin,t_xmax,t_field3 as t_cid,t_ctid,t_infomask2,t_infomask from heap_page_items(get_raw_page('t_mvcc1',0));
 lp | lp_off | lp_flags | t_xmin | t_xmax | t_cid | t_ctid | t_infomask2 | t_infomask 
----+--------+----------+--------+--------+-------+--------+-------------+------------
  1 |   8152 |        1 |   2300 |   2302 |     0 | (0,3)  |       16386 |       1282
  2 |   8112 |        1 |   2301 |   2302 |     1 | (0,4)  |       16386 |       1282
  3 |   8072 |        1 |   2302 |   2304 |     0 | (0,3)  |       40962 |       9474
  4 |   8032 |        1 |   2302 |   2303 |     1 | (0,6)  |       49154 |      10498
  5 |   7992 |        1 |   2303 |      0 |     0 | (0,5)  |       32770 |      10754
  6 |   7952 |        1 |   2303 |      0 |     1 | (0,6)  |       32770 |      10754
(6 rows)

3号(lp=3) tuple被删除,t_xmax修改为2304,t_ctid修改为(0,3).
t_infomask2=40962,即0xA002
t_infomask=9474,即0x2502 -> HEAP_UPDATED | HEAP_XMAX_COMMITTED | HEAP_XMIN_COMMITTED | HEAP_HASVARWIDTH

删除数据(回滚事务)

testdb=# begin;
BEGIN
testdb=# 
testdb=# delete from t_mvcc1 where c1 = 2;
DELETE 1
testdb=# 
testdb=# rollback;
ROLLBACK
testdb=# 
testdb=# select cmin,cmax,xmin,xmax,ctid,c1,c2 from t_mvcc1;
 cmin | cmax | xmin | xmax | ctid  | c1 |  c2  
------+------+------+------+-------+----+------
    0 |    0 | 2302 | 2305 | (0,4) |  2 | C2#2
(1 row)

xmax修改为事务号2305(原为2303).
通过pageinspect插件查看page中的内容

testdb=# select lp,lp_off,lp_flags,t_xmin,t_xmax,t_field3 as t_cid,t_ctid,t_infomask2,t_infomask from heap_page_items(get_raw_page('t_mvcc1',0));
 lp | lp_off | lp_flags | t_xmin | t_xmax | t_cid | t_ctid | t_infomask2 | t_infomask 
----+--------+----------+--------+--------+-------+--------+-------------+------------
  1 |   8152 |        1 |   2300 |   2302 |     0 | (0,3)  |       16386 |       1282
  2 |   8112 |        1 |   2301 |   2302 |     1 | (0,4)  |       16386 |       1282
  3 |   8072 |        1 |   2302 |   2304 |     0 | (0,3)  |       40962 |       9474
  4 |   8032 |        1 |   2302 |   2305 |     0 | (0,4)  |       40962 |      10498
  5 |   7992 |        1 |   2303 |      0 |     0 | (0,5)  |       32770 |      10754
  6 |   7952 |        1 |   2303 |      0 |     1 | (0,6)  |       32770 |      10754
(6 rows)

删除4号(lp=4) tuple,但事务回滚,t_max修改为2305.
t_infomask2=40962,即0xA002
t_infomask=10498,即0x2902 -> HEAP_UPDATED | HEAP_XMAX_INVALID | HEAP_XMIN_COMMITTED | HEAP_HASVARWIDTH

四、参考资料

Concurrency Control
Understanding System Columns in PostgreSQL

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容