一、 redo函数
确定日志来源之后,就可以开始应用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函数。
二、 XLogReadBufferForRedo函数
这个函数内容很简单,就是再调用XLogReadBufferForRedoExtended函数,但是注释很长,我们来学习学习。
1. 主要作用
读取需要修改的页面,并根据LSN确认是否需要进行redo。如果WAL记录中包含full-page image,则需要还原该页。
2. 返回值
返回以下值之一:
- BLK_NEEDS_REDO:该日志记录需要执行应用
- BLK_DONE:块不需要replay
- BLK_RESTORED:该块还原自全页写记录
- BLK_NOTFOUND:找不到该块
3. 主要参数
- record:WAL记录。其中的EndRecPtr用于与page LSN比较,以确定该记录是否需要执行redo(是否已经落盘)
- block_id:在WAL记录创建时注册的块ID号
- buf:将WAL记录读入的缓冲区
/*
* 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函数
/*
* 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