尽管所有内置的 WAL 日志模块都有各自的 WAL 记录类型,但还存在一种通用的 WAL 记录类型,它以通用的方式描述对页面的修改。
通用 WAL 记录在逻辑解码期间会被忽略。 如果您的扩展需要逻辑解码,请考虑使用自定义 WAL 资源管理器。
用于构造通用 WAL 记录的 API 定义在
access/generic_xlog.h 中,实现位于
access/transam/generic_xlog.c。
要使用通用 WAL 记录机制执行一次 WAL 日志化的数据更新,请按照以下步骤操作:
state = GenericXLogStart(relation) — 开始为
给定关系构造一条通用 WAL 记录。
page = GenericXLogRegisterBuffer(state, buffer, flags)
— 注册一个将在当前通用 WAL 记录中被修改的缓冲区。该函数返回一个
指向缓冲区页面临时副本的指针,修改应在这个副本上进行。(不要直接修改
缓冲区的内容。)第三个参数是适用于该操作的标志位掩码。目前唯一的此类
标志是 GENERIC_XLOG_FULL_IMAGE,它表示 WAL 记录中应包含
完整页面镜像而非增量更新。通常当页面是新建的或已被完全重写时,才设置
该标志。如果 WAL 日志化的操作需要修改多个页面,
GenericXLogRegisterBuffer 可以被多次调用。
对上一步中获得的页面镜像应用修改。
GenericXLogFinish(state) — 将修改应用到缓冲区,
并发出这条通用 WAL 记录。
在上述任意步骤之间,都可以通过调用
GenericXLogAbort(state) 来取消 WAL 记录的构造。
这将丢弃对页面镜像副本所做的全部修改。
使用通用 WAL 记录机制时请注意以下几点:
不允许直接修改缓冲区!所有修改都必须在从
GenericXLogRegisterBuffer() 获得的副本上进行。
换言之,构造通用 WAL 记录的代码绝不能自己去调用
BufferGetPage()。不过,在恰当的时机对缓冲区进行
pin/unpin 和加锁/解锁仍是调用者的责任。从调用
GenericXLogRegisterBuffer() 之前直到
GenericXLogFinish() 之后,每个目标缓冲区都必须持有
排他锁。
注册缓冲区(第 2 步)与修改页面镜像(第 3 步)可以自由交错进行, 即这两个步骤可以以任意顺序重复。请记住,缓冲区的注册顺序应当与重放 期间对它们获取锁的顺序一致。
一条通用 WAL 记录可注册的缓冲区数量上限为
MAX_GENERIC_XLOG_PAGES。超过该上限将抛出错误。
通用 WAL 假定待修改的页面采用标准布局,特别是
pd_lower 与 pd_upper
之间没有有用的数据。
由于您修改的是缓冲区页面的副本,GenericXLogStart()
并不会进入临界区。因此,在 GenericXLogStart() 与
GenericXLogFinish() 之间,您可以安全地进行内存分配、
抛出错误等操作。真正的临界区只存在于
GenericXLogFinish() 内部。在错误退出路径中调用
GenericXLogAbort() 也无需担心。
GenericXLogFinish() 会负责将缓冲区标记为脏并设置其
LSN。您无需显式地做这些事。
对于非日志表(unlogged)关系,一切的工作方式都相同,只是不会真正发出 WAL 记录。因此,您通常不需要为非日志关系做任何显式检查。
通用 WAL 重做函数会按照注册时的顺序获取缓冲区的排他锁。完成所有重做 之后,锁也会按相同的顺序释放。
如果注册缓冲区时未指定 GENERIC_XLOG_FULL_IMAGE,通用
WAL 记录中将包含新旧页面镜像之间的增量。该增量基于逐字节的比较。对于
在页面内移动数据的情形,这种方式并不十分紧凑,未来可能会加以改进。