确定日志来源之后,就可以开始应用WAL日志。在Rmgr中,每种类型的WAL日志都有startup,redo,cleanup等函数,其中最重要的就是redo函数。
以最常见的insert为例,假如每个事务执行了插入并提交,此时数据还在buffer没有落盘,恰逢数据库宕机。在db下次启动时,就会读取到XLOG_HEAP_INSERT类型的WAL记录(对应Rmgr类型为RM_HEAP_ID),因此会进入heap_redo函数(在heapam.c文件)。
heap_redo函数主要就是一个switch语句,根据不同类型的WAL记录,执行不同的操作。对于XLOG_HEAP_INSERT,则执行heap_xlog_insert。
- void
- heap_redo(XLogReaderState *record)
- {
- uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-
- /*
- * These operations don't overwrite MVCC data so no conflict processing is
- * required. The ones in heap2 rmgr do.
- */
-
- switch (info & XLOG_HEAP_OPMASK)
- {
- case XLOG_HEAP_INSERT:
- heap_xlog_insert(record);
- break;
- case XLOG_HEAP_DELETE:
- heap_xlog_delete(record);
- break;
- case XLOG_HEAP_UPDATE:
- heap_xlog_update(record, false);
- break;
- case XLOG_HEAP_TRUNCATE:
-
- /*
- * TRUNCATE is a no-op because the actions are already logged as
- * SMGR WAL records. TRUNCATE WAL record only exists for logical
- * decoding.
- */
- break;
- …
- default:
- elog(PANIC, "heap_redo: unknown op code %u", info);
- }
- }
heap_xlog_insert函数涉及大量函数调用,目前还看不懂,暂时略过。其中跟日志应用相关的主要是XLogReadBufferForRedo函数。
这个函数内容很简单,就是再调用XLogReadBufferForRedoExtended函数,但是注释很长,我们来学习学习。
读取需要修改的页面,并根据LSN确认是否需要进行redo。如果WAL记录中包含full-page image,则需要还原该页。
返回以下值之一:
- /*
- * XLogReadBufferForRedo
- * Read a page during XLOG replay
- */
-
- XLogRedoAction
- XLogReadBufferForRedo(XLogReaderState *record, uint8 block_id,
- Buffer *buf)
- {
- return XLogReadBufferForRedoExtended(record, block_id, RBM_NORMAL,
- false, buf);
- }
- /*
- * XLogReadBufferForRedoExtended
- * Like XLogReadBufferForRedo, but with extra options.
- */
- XLogRedoAction
- XLogReadBufferForRedoExtended(XLogReaderState *record,
- uint8 block_id,
- ReadBufferMode mode, bool get_cleanup_lock,
- Buffer *buf)
- {
- …
-
- /* If it has a full-page image and it should be restored, do it. 全页写的块 */
- if (XLogRecBlockImageApply(record, block_id))
- {
- Assert(XLogRecHasBlockImage(record, block_id));
- *buf = XLogReadBufferExtended(rnode, forknum, blkno,
- get_cleanup_lock ? RBM_ZERO_AND_CLEANUP_LOCK : RBM_ZERO_AND_LOCK);
- page = BufferGetPage(*buf);
- if (!RestoreBlockImage(record, block_id, page))
- elog(ERROR, "failed to restore block image");
- …
- return BLK_RESTORED;
- }
- /* 非全页写的块 */
- else
- {
- *buf = XLogReadBufferExtended(rnode, forknum, blkno, mode);
- if (BufferIsValid(*buf))
- {
- …
- /* 如果页面LSN >= 日志记录LSN,说明该记录已应用到页面 */
- if (lsn <= PageGetLSN(BufferGetPage(*buf)))
- return BLK_DONE;
- else
- return BLK_NEEDS_REDO;
- }
- else
- return BLK_NOTFOUND;
- }
- }
参考
《PostgreSQL技术内幕:事务处理深度探索》第4章
https://www.bookstack.cn/read/aliyun-rds-core/393e144490c30cb5.md