PostgreSQL的恢复功能基于重做日志(REDO log)实现。如果数据库服务器崩溃,PostgreSQL通过从REDO点依序重放WAL段文件中的XLOG记录来恢复数据库集群。
在本节之前,我们已经多次讨论过数据库恢复,所以这里将会介绍两个与恢复有关,但尚未解释过的事情。
第一件事是PostgreSQL如何启动恢复过程。当PostgreSQL启动时,它首先读取pg_control
文件。以下是从那时起恢复处理的细节。参见图9.14和以下描述。
图9.14 恢复过程的细节
- PostgreSQL在启动时读取
pg_control
文件的所有项。如果state
项是in production
,PostgreSQL将进入恢复模式,因为这意味着数据库没有正常停止;如果是shut down
,它就会进入正常的启动模式。 - PostgreSQL从合适的WAL段文件中读取最近的检查点,该记录的位置写在
pg_control
文件中,并从该检查点中获得重做点。如果最新的检查点是无效的,PostgreSQL会读取前一个检查点。如果两个记录都不可读,它就会放弃自我恢复(注意在PostgreSQL11中不会存储前一个检查点)。 - 使用合适的资源管理器按顺序读取并重放XLOG记录,从重做点开始,直到最新WAL段文件的最后位置。当遇到一条属于备份区块的XLOG记录时,无论其LSN如何,它都会覆写相应表的页面。其他情况下,只有当此记录的LSN大于相应页面的
pd_lsn
时,才会重放该(非备份区块的)XLOG记录。
第二件事是关于LSN的比较:为什么应该比较非备份区块的LSN和相应页面的pd_lsn
。与前面的示例不同,这里使用需要在两个LSN之间进行比较的具体例子来解释,如图9.15和图9.16。 (注意这里省略了WAL缓冲区,以简化描述)。
图9.15 当后台写入者工作时的插入操作
- PostgreSQL将一条元组插入表A,并将一条XLOG记录写入
LSN_1
。 - 后台写入者进程将表A的页面写入存储。此时,此页面的
pd_lsn
为LSN_1
。 - PostgreSQL在表A中插入一条新元组,并在
LSN_2
处写入一条XLOG记录。修改后的页面尚未写入存储。
与本章概述中的例子不同,在本场景中,表A的页面已经被一次性写入存储中。使用immediate
模式关闭数据库,然后启动数据库。
图9.16 数据库恢复
- PostgreSQL加载第一条XLOG记录和表A的页面,但不重放它,因为该记录的LSN不大于表A的LSN(两个值都是
LSN_1
)。实际上一目了然,没有重放该记录的必要性。 - 接下来,PostgreSQL会重放第二条XLOG记录,因为该记录的LSN(
LSN_2
)大于当前表A的LSN(LSN_1
)。
从这个例子中可以看出,如果非备份区块的重放顺序不正确,或者多次重放非备份区块,数据库集群将不再一致。简而言之,非备份区块的重做(重放)操作不是幂等(idempotent) 的。因此,为了确保正确的重放顺序,非备份区块中的记录当且仅当其LSN大于相应页面的pd_lsn
时,才执行重放。
另一方面,由于备份区块的重放操作是幂等的,不管其LSN为何值,备份块可以重放任意次。