完成了热身练习后,现在我们已经做好理解XLOG记录写入过程的准备了。因此在本节中,我将尽可能仔细地描述。首先,以下列语句的执行为例,让我们来看一看PostgreSQL的内幕。
testdb=# INSERT INTO tbl VALUES ('A');
通过发出上述语句,内部函数exec_simple_query()
会被调用,其伪代码如下所示:
exec_simple_query() @postgres.c
(1) ExtendCLOG() @clog.c /* 将当前事务的状态"IN_PROGRESS"写入CLOG */
(2) heap_insert() @heapam.c /* 插入元组,创建一条XLOG记录并调用函XLogInsert. */
(3) XLogInsert() @xlog.c /* (9.5 以及后续的版本为 xloginsert.c) */
/* 将插入元组的XLOG记录写入WAL缓冲区,更新页面的 pd_lsn */
(4) finish_xact_command() @postgres.c /* 执行提交 */
XLogInsert() @xlog.c /* (9.5 以及后续的版本为 xloginsert.c) */
/* 将该提交行为的XLOG记录写入WAL缓冲区 */
(5) XLogWrite() @xlog.c /* 将WAL缓冲区中所有的XLOG刷写入WAL段中 */
(6) TransactionIdCommitTree() @transam.c
/* 在CLOG中将当前事务的状态由"IN_PROGRESS"修改为"COMMITTED" /*
在接下来的段落中将会解释每一行伪代码,从而理解XLOG记录写入的过程。如图9.11和图9.12所示。
- 函数
ExtendCLOG()
将当前事务的状态IN_PROGRESS
写入内存中的CLOG。 - 函数
heap_insert()
向共享缓冲池的目标页面中插入堆元组,创建当前页面的XLOG记录,并执行函数XLogInsert()
。 - 函数
XLogInsert()
会将heap_insert()
创建的XLOG记录写入WAL缓冲区LSN_1
处,并将被修改页面的pd_lsn
从LSN_0
更新为LSN_1
。 - 函数
finish_xact_command()
会在该事务被提交时被调用,用于创建该提交动作的XLOG记录,而这里的XLogInsert()
函数会将该记录写入WAL缓冲区LSN_2
处。 上图的XLOG格式是9.4版本的 - 函数
XLogWrite()
会冲刷WAL缓冲区,并将所有内容写入WAL段文件中。如果wal_sync_method
参数被配置为open_sync
或open_datasync
,记录会被同步写入(译者注:而不是提交才会刷新WAL缓冲区),因为函数会使用带有O_SYNC
或O_DSYNC
标记的open()
系统调用。如果该参数被配置为fsync
,fsync_writethrough
,fdatasync
,相应的系统调用就是fsync()
,带有F_FULLSYNC
选项的fcntl()
,以及fdatasync()
。无论哪一种情况,所有的XLOG记录都会被确保写入存储之中。 - 函数
TransactionIdCommitTree()
将提交日志clog中当前事务的状态从IN_PROGRESS
更改为COMMITTED
。 图9.12 XLOG记录的写入顺序(续图9.11)
在上面这个例子中,COMMIT
操作致使XLOG记录写入WAL段文件。但发生在下列任一情况时,都会执行这种写入操作:
- 一个运行中的事务提交或中止。
- WAL缓冲区被写入的元组填满(WAL缓冲区的大小由参数
wal_buffers
控制) - WAL写入者进程周期性执行写入(参见 下一节 )
如果出现上述情况之一,无论其事务是否已提交,WAL缓冲区上的所有WAL记录都将写入WAL段文件中。
DML操作写XLOG记录是理所当然的,但非DML操作也会产生XLOG。如上所述,COMMIT
操作会写入包含着提交的事务ID的XLOG记录。另一个例子是Checkpoint
操作会写入关于该检查点概述信息的XLOG记录。此外,尽管不是很常见,SELECT
语句在一些特殊情况下也会创建XLOG记录。例如在SELECT语句处理的过程中,如果因为HOT(Heap Only Tuple)需要删除不必要元组并拼接必要的元组时,修改对应页面的XLOG记录就会写入WAL缓冲区。