什么是WAL?

顾名思义,就是写在前面的日志,是事物和数据库故障的一个保护。任何试图修改数据库数据的操作都会写一份日志到磁盘。这个日志在PG中叫XLOG,所有的日志都会写在$PGDATA/pg_wal目录下面。每个XLOG的Page和Seg的默认大小:source:src/include/pg_config.h

PostgreSQL数据库WAL——初始化_数据库


Page大小为XLOG_BLCKSZ(8K),Seg大小为XLOG_SEG_SIZE(16M)

PostgreSQL数据库WAL——初始化_初始化_02


只允许在编译的时候进行修改,例如:./configure --with-wal-segsize=64就是修改segsize为64M。

PostgreSQL数据库WAL——初始化_初始化_03


一个段文件的名字由24个十六进制字符组成,分为三个部分,每个部分由8个十六进制字符组成。第一部分表示时间线,第二部分表示日志文件标号,第三部分表示日志文件的段标号。时间线由1开始,日志文件标号和日志文件的段标号由0开始,所以系统中的第一个事务日志文件是000000010000000000000000,第二个事务日志文件是000000010000000000000001。

PostgreSQL数据库WAL——初始化_数据库_04


PostgreSQL数据库WAL——初始化_初始化_05

数据库初始化

初始化数据库执行​​initdb -D $PGDATA​​​后,会初始化一个数据库,在​​$PGDATA/pg_wal​​目录下默认会生成一个xlog文件。

PostgreSQL数据库WAL——初始化_日志文件_06


该文件是通过管道(popen)调用postgres这个命令生成的:/usr/local/pg_10_3/bin/postgres --boot -x1 -F

PostgreSQL数据库WAL——初始化_数据库_07


这个命令会初始化很多东西,如下图:

PostgreSQL数据库WAL——初始化_日志文件_08


我们先看看XLOG,初始化XLOG的函数入口为BootStrapXLOG,这个函数只会在初始化的时候调用一次,用来创建控制文件和初始化XLOG segment。

source:src/backend/access/transam/xlog.c+4959

BootStrapXLOG(void)

PostgreSQL数据库WAL——初始化_日志文件_09


初始化的第一个timelineID = 1:

PostgreSQL数据库WAL——初始化_初始化_10


该值是标识数据库状态用的。PG的解释如下:

PostgreSQL数据库WAL——初始化_日志文件_11


创建的第一个XLOG segment文件 = 1:

PostgreSQL数据库WAL——初始化_初始化_12


所以宏XLogFilePath的结果为,path = pg_wal/000000010000000000000001:

PostgreSQL数据库WAL——初始化_日志文件_13


在创建这个文件之前,PG会创建个临时文件:

PostgreSQL数据库WAL——初始化_日志文件_14


pg_wal/xlogtemp.29191("xlogtemp."加个当前进程PID),避免其他process来搞事情,然后循环写入每次写入XLOG_BLOCKSZ个字节

PostgreSQL数据库WAL——初始化_数据库_15


写完之后,文件为16M:

PostgreSQL数据库WAL——初始化_数据库_16


这个文件内容其实什么都没是个空文件。然后通过函数durable_link_or_rename进行rename。

PostgreSQL数据库WAL——初始化_日志文件_17


到这里,XLOG就创建完成了,不过这个文件还是空空如也,首先写入的是XLOG的头(XLogPageHeader)。

PostgreSQL数据库WAL——初始化_日志文件_18


写入XLOG文件的内容如下:

PostgreSQL数据库WAL——初始化_初始化_19


这个page的前40个字节就是XLogLongPageHeaderData(由于每个XLogSegment的第一个page头会写入XLogLongPageHeaderData)。xlp_seg_size是当前xlog_segment大小=1024102416;xlp_xlog_blcksz是当前xlog的page大小=8192。结果如下:

PostgreSQL数据库WAL——初始化_数据库_20


写入完之后整个的XLOG_SEGMENT的代码如下图所示:

PostgreSQL数据库WAL——初始化_初始化_21

数据库启动初始化

前面的是数据库初始化的一个过程,现在是看看数据库启动的时候是如何进行初始化XLOG的呢?

在数据库启动的时候,会从系统申请一份WAL的共享内存。入口函数是XLOGShmemInit(void)(ControlFile也在这个里面初始化共享内存)。

如果数据库不修改任何参宿,默认会初始化4209288字节的内存空间。通过函数XLOGShmemSize(void)计算出来的。内存大小跟GUC参数wal_buffers强相关,因为默认为-1所以PG会用XLOGbuffers = NBuffers / 32 = 512。

source:src/backend/access/transam/xlog.c+4824

WAL的缓冲区和控制结构在共享内存里,它们是用轻量级锁保护的,对共享内存的需求由缓冲区数量决定,默认的WAL缓冲区大小是8个8KB的缓冲区(64KB)。

PostgreSQL数据库WAL——初始化_日志文件_22


分配的共享内存分布如下图所示:

PostgreSQL数据库WAL——初始化_数据库_23


XLogCtlData:1544字节

XLogRecPtr[]:4096字节(8512(XLOGBuffers)) = 4096
WALInsertLockPadded:1152字节(128
9)

XLOG_BLCKSZ:8192字节

XLOGbuffers:4194304字节(8192512(XLOG_BLCKSZ,XLOGbuffers))

初始化头结构体:

XLogCtl->xlblocks = 上图中的XLogRecPtr。

WALInsertLocks = XLogCtl->Insert.WALInsertLocks = 上图中的WALInsertLockPadded。

XLogCtl->pages = 上图中的XLOGbuffers,这个buffer就是后面存储wal的地方,所有的xlog都会先写入到这个内存中,然后再刷到磁盘中。

NOTE:因为XLogCtl结构体很大,大家可以对照的源码看看。共享内存的指针都挂载这个头结构体中。