天天看点

[2021-06-17]PostgreSQL事物提交日志信息clog解析

       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]$