数据库代码中事务ID的类型TransactionId定义为:typedef uint32 TransactionId。因此事务ID最大值为2^32-1=4294967295。
事务ID是需要循环使用的,为了做到这一点,数据库在做vacuum时将很老的事务ID冷冻,来完成旧事务ID的回收。
下面就讲解事务ID冷冻的本质:
举例说明事务ID冷冻
highgo=# insert into t1 values(1,1); INSERT 0 1 highgo=# SELECT * FROM heap_page_items(get_raw_page('t1', 0)); lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data ----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+-------------------- 1 | 8160 | 1 | 32 | 1246 | 0 | 0 | (0,1) | 2 | 2048 | 24 | | | \x0100000001000000 (1 row) highgo=# vacuum ; VACUUM highgo=# SELECT * FROM heap_page_items(get_raw_page('t1', 0)); lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data ----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+-------------------- 1 | 8160 | 1 | 32 | 1246 | 0 | 0 | (0,1) | 2 | 2304 | 24 | | | \x0100000001000000 (1 row) highgo=# vacuum full; VACUUM highgo=# SELECT * FROM heap_page_items(get_raw_page('t1', 0)); lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data ----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+-------------------- 1 | 8160 | 1 | 32 | 1246 | 0 | 0 | (0,1) | 2 | 2816 | 24 | | | \x0100000001000000 (1 row) highgo=#
如上图向t1表插入一行数据之后,查询其page数据,做vacuum full后再次查询其page数据。
发现其t_infomask属性发生了变化:2816-2048=768=300(十六进制)(做vacuum后t_infomask变化没有这么大,原因后面会讲到)
在代码中有如下宏值
#define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */ #define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */ #define HEAP_XMIN_FROZEN (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)
事务ID1026冷冻,就是将所有xmin为1026的tuple的t_infomask置为t_infomask | HEAP_XMIN_FROZEN
事务ID冰冻相关代码
做vacuum时,原则上需要遍历所有的tuple,遍历时通过heap_prepare_freeze_tuple()函数判断tuple的xmin是否需要被冰冻
如果需要被冰冻,则通过heap_execute_freeze_tuple()执行冰冻处理
注意:
代码逻辑->
当tuple的xmin小于cutoff_xid时,会判断需要做冰冻处理。
如下为做lazy vacuum时和做vacuum full时调用heap_prepare_freeze_tuple的入参情况。
可见当lazy vacuum时cutoff_xid为一个很大的值,因此当tuple的xmin大到一个阀值后,lazy vacuum才会冰冻事务ID
--lazy vacuum Breakpoint 3, heap_prepare_freeze_tuple ( tuple=0x7f85e91fa2f8, cutoff_xid=4244968248, cutoff_multi=4289967297, frz=frz@entry=0x1046618, totally_frozen_p=totally_frozen_p@entry=0x7fffcb3736a0 "") at heapam.c:6672 6672 if (TransactionIdPrecedes(xid, cutoff_xid)) --vacuum full Breakpoint 3, heap_prepare_freeze_tuple ( tuple=tuple@entry=0x10475b0, cutoff_xid=952, cutoff_multi=1, frz=frz@entry=0x7fffcb3734e0, totally_frozen_p=totally_frozen_p@entry=0x7fffcb3734df "") at heapam.c:6672 6672 if (TransactionIdPrecedes(xid, cutoff_xid))
事务ID大小比较的玄机
代码中有很多事务ID大小比较的情况,循环使用事务ID后,如何保证事务ID大小比较是符合逻辑的呢。
bool TransactionIdPrecedes(TransactionId id1, TransactionId id2) { /* * If either ID is a permanent XID then we can just do unsigned * comparison. If both are normal, do a modulo-2^32 comparison. */ int32 diff; if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2)) return (id1 < id2); diff = (int32) (id1 - id2); return (diff < 0); }
如上一个比较事务ID大小的函数。
将两个unsigned int 做差,然后转化为signed int.
比如:
id1 = 2^32 -1 = 4294967295
id2 = 952
那么
id1 -id2=4294966343
diff = int(id1-id2)=-953
函数返回结果id1小于id2,即4294967295小于952