clog详解

2,057 阅读6分钟

网上关于clog的介绍比较多,但相对零散,而搜索总是找到些大同小异的知识点,标题党居多。所以希望能整合下相关内容。

定义

clog记录的是事务的最终状态,需要对比的是xlog,xlog除了记录事务最终状态之外,还记录着事务的执行过程。

PostgreSQL中的事务共有四种状态:

#define TRANSACTION_STATUS_IN_PROGRESS      0x00 //事务正在执行
#define TRANSACTION_STATUS_COMMITTED        0x01 //事务已提交
#define TRANSACTION_STATUS_ABORTED          0x02 //事务被中止
#define TRANSACTION_STATUS_SUB_COMMITTED    0x03 //子事务已提交

因此,一个事务的状态可以用2bit来表示,如01。所以一个字节可以存放4个事务的状态,而PG系统定义每个page大小为8K(定义于/src/include/pg_config.h: #define BLCKSZ 8192),故而一个page中可以存放4*8K=2^{15}=32768个事务的状态。

  1. 在物理磁盘上,clog存储是以segment为文件单位,一个segment为一个单独的clog文件,而每个segment中有SLRU_PAGES_PER_SEGMENT = 32个page
  2. 在共享内存中,clog被放在SLRU缓存池,每份大小为一个page,总个数如下。其中NBuffers默认值为4096,因此下面这个函数的返回值就是8

NBuffers也可以作为postgres参数自由设置,但通常并无调优的必要,具体可参见Postgresql官方文档

Size CLOGShmemBuffers(void){
	return Min(32, Max(4, NBuffers / 512));
}

clog布局

因此,一个事务在clog中的状态可以根据4元素来查找到,即<Segment偏移,Segment中page偏移,page中字节偏移,字节内位偏移>。其中,Segment偏移是当磁盘中存在多个Segment文件时有效。

#功能

使用

为提高处理速度,新增事务的状态都是先储存在SLRU缓存池中,只有当一个page全部写满之后才会一次性落盘,当然,此时你也可以通过sql命令checkpoint立即落盘。

而想要从clog中读取某个事务的状态时,也是先从缓存池中读取,如果缓存池中没有,那么会从segment中拿到对应的page,然后把整个page的数据加载到缓存中,再进行读取。

题外话,clog的相关流程都清晰简单,想要了解pg系统中的log类,可以考虑从clog开始

初始化与文件增长

在pg系统中,数据库经initdb安装时会初始化一个page大小的clog文件。此后,一旦事务id超过32678的倍数时,将触发自动落盘(或者在此之前手动触发落盘),该文件大小将增加8K。直到满足一个segment大小后,系统将重新生成一个segment文件

在新的clog缓存生成时,所有数据的初始值都是0,如果让clog这时候落盘,会看到对应的二进制文件中全部是0

举例验证

下面在pg系统中对clog的状态及文件系统进行验证。在此之前,先说明下Linux下查看二进制文件的方法:

  1. 对clog的二进制文件,直接用vim打开:vim 0000
  2. 这时候看到的是一堆乱码,然后在文件中按:输入命令,合起来为:%!xxd -b,然后回车,这时候就能看到正确的二进制比特数据

注意:该二进制文件中记录的数据也有大小端区分,因为我这台机器的CPU是Intel的,经确认,属于小端模式

此处不再赘述如何安装xxd。而且如果不采用xxd的话,也可以在windows系统中,采用其它读取二进制文件的方式,如notepad++插件

步骤如下:

  1. 先查看下当前的事务号,然后利用checkpoint将缓存中的数据写入到磁盘。注意,虽然申请事务号看似什么都没做,但是在pg中,这也代表事务被正确提交了,即占用了一个事务号
postgres=# select txid_current();
 txid_current 
--------------
         1319
(1 row)

postgres=# checkpoint;
CHECKPOINT
Xum:/home/postgres/install/data/pg_clog # ll
total 8
-rw------- 1 postgres postgres 8192 Aug  7 08:51 0000
  1. 打开clog文件,输入:set nu命令打开行号,查看当前的最新的一个非00值,记下相应位置,这就是当前最新的一个事务状态了,记录下地址位是000014c,所在行是56,最新的状态为01,即事务已提交
  54 000013e: 01010101 01010101 01010101 01010101 01010101 01010101  UUUUUU
  55 0000144: 01010101 01010101 01010101 01100101 10100110 01100101  UUUe.e
  56 000014a: 01100101 10011001 00000001 00000000 00000000 00000000  e.....
  1. 在PostgreSQL中开启事务,然后提交或者回滚,这里我连续实验了多个事务:一个回滚,一个只是占用了事务号,另一个正确提交。因此,这三个事务的状态分别是10,01,01
test=# begin;
BEGIN
test=# select txid_current();
 txid_current 
--------------
         1329
(1 row)

test=# insert into t values(3,'dahuang');
INSERT 0 1
test=# rollback;
ROLLBACK
test=# select txid_current();
 txid_current 
--------------
         1330
(1 row)

test=# begin;
BEGIN
test=# insert into t values(3,'dahuang');
INSERT 0 1
test=# commit;
COMMIT
  1. 然后用checkpoint将缓存中的clog数据落盘,再次查看clog文件的更新时间,可以发现此时的时间被刷新了
Xum:/home/postgres/install/data/pg_clog # ll
total 8
-rw------- 1 postgres postgres 8192 Aug  7 08:56 0000
  1. 重新打开clog文件,直接用:56定位到之前的行。按照之前的分析,以及Intel X86 CPU中大小端的影响,刚好000014c这个地址下还有6bit可以用于存放,所以上述三个事务在二进制文件中存放的就是010110
  54 000013e: 01010101 01010101 01010101 01010101 01010101 01010101  UUUUUU
  55 0000144: 01010101 01010101 01010101 01100101 10100110 01100101  UUUe.e
  56 000014a: 01100101 10011001 01011001 00000000 00000000 00000000  e.Y...

参考资料

  1. 对PostgreSQL中NBuffers的理解