9.8 PostgreSQL中的数据库恢复

PostgreSQL的恢复功能基于重做日志(REDO log)实现。如果数据库服务器崩溃,PostgreSQL通过从REDO点依序重放WAL段文件中的XLOG记录来恢复数据库集群。

在本节之前,我们已经多次讨论过数据库恢复,所以这里将会介绍两个与恢复有关,但尚未解释过的事情。

第一件事是PostgreSQL如何启动恢复过程。当PostgreSQL启动时,它首先读取pg_control文件。以下是从那时起恢复处理的细节。参见图9.14和以下描述。

图9.14 恢复过程的细节图9.14 恢复过程的细节

  1. PostgreSQL在启动时读取pg_control文件的所有项。如果state项是in production,PostgreSQL将进入恢复模式,因为这意味着数据库没有正常停止;如果是shut down,它就会进入正常的启动模式。
  2. PostgreSQL从合适的WAL段文件中读取最近的检查点,该记录的位置写在pg_control文件中,并从该检查点中获得重做点。如果最新的检查点是无效的,PostgreSQL会读取前一个检查点。如果两个记录都不可读,它就会放弃自我恢复(注意在PostgreSQL11中不会存储前一个检查点)。
  3. 使用合适的资源管理器按顺序读取并重放XLOG记录,从重做点开始,直到最新WAL段文件的最后位置。当遇到一条属于备份区块的XLOG记录时,无论其LSN如何,它都会覆写相应表的页面。其他情况下,只有当此记录的LSN大于相应页面的pd_lsn时,才会重放该(非备份区块的)XLOG记录。

第二件事是关于LSN的比较:为什么应该比较非备份区块的LSN和相应页面的pd_lsn。与前面的示例不同,这里使用需要在两个LSN之间进行比较的具体例子来解释,如图9.15和图9.16。 (注意这里省略了WAL缓冲区,以简化描述)。

图9.15 当后台写入者工作时的插入操作图9.15 当后台写入者工作时的插入操作

  1. PostgreSQL将一条元组插入表A,并将一条XLOG记录写入LSN_1
  2. 后台写入者进程将表A的页面写入存储。此时,此页面的pd_lsnLSN_1
  3. PostgreSQL在表A中插入一条新元组,并在LSN_2处写入一条XLOG记录。修改后的页面尚未写入存储。

与本章概述中的例子不同,在本场景中,表A的页面已经被一次性写入存储中。使用immediate模式关闭数据库,然后启动数据库。

图9.16 数据库恢复图9.16 数据库恢复

  1. PostgreSQL加载第一条XLOG记录和表A的页面,但不重放它,因为该记录的LSN不大于表A的LSN(两个值都是LSN_1)。实际上一目了然,没有重放该记录的必要性。
  2. 接下来,PostgreSQL会重放第二条XLOG记录,因为该记录的LSN(LSN_2)大于当前表A的LSN(LSN_1)。

从这个例子中可以看出,如果非备份区块的重放顺序不正确,或者多次重放非备份区块,数据库集群将不再一致。简而言之,非备份区块的重做(重放)操作不是幂等(idempotent) 的。因此,为了确保正确的重放顺序,非备份区块中的记录当且仅当其LSN大于相应页面的pd_lsn时,才执行重放。

另一方面,由于备份区块的重放操作是幂等的,不管其LSN为何值,备份块可以重放任意次。