PG事物提交之后,会把事务的状态信息写入到clog(pg_xact)文件中,当第一次访问事物涉及到的行时,会读取clog记录的事物状态信息来判断事物是否提交或者abort,并且更新t_infomask的状态,这样下次再访问该行数据时通过判断t_infomask就知道不再需要访问clog了。具体t_infomask的具体值和意义,可以参考https://blog.csdn.net/m15217321304/article/details/110870837
--//源码位置 src/backend/access/transam/clog.c
--//xid事物的状态信息可以在头文件查看
--//头文件位置src/include/access/clog.h
/*
* Possible transaction statuses --- note that all-zeroes is the initial
* state.
*
* A "subcommitted" transaction is a committed subtransaction whose parent
* hasn't committed or aborted yet.
*/
typedef int XidStatus;
#define TRANSACTION_STATUS_IN_PROGRESS 0x00
#define TRANSACTION_STATUS_COMMITTED 0x01
#define TRANSACTION_STATUS_ABORTED 0x02
#define TRANSACTION_STATUS_SUB_COMMITTED 0x03
--//源码位置 src/backend/access/transam/clog.c
/*
* Defines for CLOG page sizes. A page is the same BLCKSZ as is used
* everywhere else in Postgres.
*
* Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF,
* CLOG page numbering also wraps around at 0xFFFFFFFF/CLOG_XACTS_PER_PAGE,
* and CLOG segment numbering at
* 0xFFFFFFFF/CLOG_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT. We need take no
* explicit notice of that fact in this module, except when comparing segment
* and page numbers in TruncateCLOG (see CLOGPagePrecedes).
*/
/* We need two bits per xact, so four xacts fit in a byte */
#define CLOG_BITS_PER_XACT 2
#define CLOG_XACTS_PER_BYTE 4
#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
#define CLOG_XACT_BITMASK ((1 << CLOG_BITS_PER_XACT) - 1)
##CLOG_BITS_PER_XACT 代表一个事务2个字节 , 一个事务需要两个字节表示,所以一个1字节(8个bit位)可以存放8/2=4个事物
##CLOG_XACTS_PER_BYTE 代表一个字节可以存放4个事物
##CLOG_XACTS_PER_PAGE 代表一个page可以存放多少事物 ,比如一个块8K, 一个字节可以存放4个事物, 所以一个page=8192*4
##CLOG_XACT_BITMASK 代表CLOG_BITS_PER_XACT左移一位,CLOG_BITS_PER_XACT原值为2,二进制为0010,左移一位0100,然后0100转换10进制为3,3 - 1 =2,转换2进制为0011
#define SLRU_PAGES_PER_SEGMENT 32
代入公式中:
N = 0xFFFFFFFF/CLOG_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT
= 0xFFFFFFFF/(8192*4)/32
= 4096
--//每个segment有32个Pages(SLRU_PAGES_PER_SEGMENT = 32),则每个segment file大小为8K*32=256K.
给定一个事务号,如何获取该事务对应的状态?
PG首先通过该事务号获得该事务状态存储在clog中哪个page,然后再根据下面的公式,计算存储事物状态的偏移量
#define TransactionIdToPage(xid) ((xid) / (TransactionId) CLOG_XACTS_PER_PAGE)
#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
#define TransactionIdToByte(xid) (TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE)
#define TransactionIdToBIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_BYTE)
如给定事务号100,根据上述公式可得到:
Page = 100 / (8192*4) = 0 —> 第0号page
PageIndex = 100 % (8192*4) = 100 —> Page内偏移
ByteInPage = 100 / 4 = 25 —> 该Page内的第25个Byte
ByteIndex = 100 % 4 = 0 —> 该字节中的首2bits
--//上面提到一个segment大小为8K*32=256K
--//我这里就遇到这么一个问题,我的xid事物号很大,如果直接套用上面的公式,那么在xact文件中找不到对应的文件
下面通过实际案例验证
--//session 1
postgres=# select txid_current();
txid_current
--------------
1048700
(1 row)
postgres=#
--//先不要提交
--//按照公式
--//1048700/(8192*4)
postgres=# select 1048700/(8192*4);
?column?
----------
32
(1 row)
postgres=#
--//但是xact文件里面没有超过32k大小的文件
[[email protected] pg_xact]$ ls -ltr
total 16
-rw------- 1 postgres postgres 8192 Jan 19 07:47 0000
-rw------- 1 postgres postgres 8192 Jun 9 00:07 0001
[[email protected] pg_xact]$
--//一个段 32个page ,一个page可以存放page*4个事物, 所以一个段最多可以存放 32*8192*4= 1048576
--//所以,如果一个xid大于了1048700,那么需要使用xid%(8192*4*32)(无论xid大小是多少,使用这个公式都没有问题)
postgres=# select 1048700/(8192*4*32);
?column?
----------
1
(1 row)
postgres=#
—-//所以,xid事物落到了0001文件
--//偏移量为1048694%(8192*4*32)
postgres=# select 1048700%(8192*4*32);
?column?
----------
124
(1 row)
--//换算偏移量对应的位置
postgres=# select 124/4;
?column?
----------
31
(1 row)
postgres=#
--//换算在31字节处的具体位置(1个字节8个bit,2个bit代表一个事务)
postgres=# select 124%4;
?column?
----------
0
(1 row)
postgres=#
--//说明在31字节的前2bit位
--//使用hexdump查看内容,当前还没有提交
[[email protected] pg_xact]$ hexdump -C 0001 -s 31 -n 1
0000001f 00 |.|
00000020
[[email protected] pg_xact]$
--//对应的进制码为 00 00 00 00
--//session 1提交会话并checkpoint
postgres=# commit;
COMMIT
postgres=# checkpoint;
CHECKPOINT
postgres=# \q
[[email protected] pg_xact]$
--//再次hexdump查看
[[email protected] pg_xact]$ hexdump -C 0001 -s 31 -n 1
0000001f 01 |.|
00000020
[[email protected] pg_xact]$
--//对应二进制码为 00 00 00 01
--//session 1 再次开启一个新事务,最后回滚
postgres=# select txid_current();
txid_current
--------------
1048701
(1 row)
postgres=# abort;
ROLLBACK
postgres=# checkpoint;
CHECKPOINT
postgres=#
--//查询clog的数据
--//#define TRANSACTION_STATUS_ABORTED 0x02
--//理论上0x2转换2进制为 10,那么31字节的bit 码应如下:
--//00 00 10 01 转换16进制为9
--//使用hexdump查看
[[email protected] pg_xact]$ hexdump -C 0001 -s 31 -n 1
0000001f 09 |.|
00000020
[[email protected] pg_xact]$
--//session 1 再次开启一个新事务,然后回滚
postgres=# begin;
BEGIN
postgres=# select txid_current();
txid_current
--------------
1048702
(1 row)
postgres=# abort;
ROLLBACK
postgres=# checkpoint;
CHECKPOINT
postgres=#
--//理论上2进制码为 00 10 10 01 ,转换16进制 29
--//使用hexdump验证
[[email protected] pg_xact]$ hexdump -C 0001 -s 31 -n 1
0000001f 29 |)|
00000020
[[email protected] pg_xact]$
--//session 1 再次开启一个新事务,最后提交
postgres=# begin;
BEGIN
postgres=# select txid_current();
txid_current
--------------
1048703
(1 row)
postgres=# commit;
COMMIT
postgres=# checkpoint;
CHECKPOINT
postgres=#
--//这个时候2进制码应该为 01 10 10 01,转换16进制为 69
--//使用hexdump验证
[[email protected] pg_xact]$ hexdump -C 0001 -s 31 -n 1
0000001f 69 |i|
00000020
[[email protected] pg_xact]$
--//如果session 1再开启一个会话,无论事务提交还是回滚,都不会再影响31字节处的数据
--//session 1开启一个会话,提交
postgres=# begin;
BEGIN
postgres=# select txid_current();
txid_current
--------------
1048704
(1 row)
postgres=# commit;
COMMIT
postgres=# checkpoint;
CHECKPOINT
postgres=#
--//使用hexdump验证
[[email protected] pg_xact]$ hexdump -C 0001 -s 31 -n 1
0000001f 69 |i|
00000020
[[email protected] pg_xact]$
--//如果验证32字节处的数据,2进制码应该是 00 00 00 01,转换16进制为01
[[email protected] pg_xact]$ hexdump -C 0001 -s 32 -n 1
00000020 01 |.|
00000021
[[email protected] pg_xact]$