PostgreSQL full_page_writes 机制分析

对于 PG 的 full page write 和 MySQL 的 double write 机制一直有些困惑,如果没有这些机制,是不是在页断裂的情况下数据一定就有问题?如果应用 redo log 或者 wal 是幂等设计的,在刷脏页的过程中,无论怎么 crash,只要支持幂等,是否就无需担心页断裂的问题。

带着上述问题,结合 PG 的实现,大致有了答案,以下分析如果有不对的,欢迎指出。

为什么 PG 没有设置 full_page_writes,就可能会导致崩溃后无法恢复?

首先这里说的崩溃是指在刷脏页过程中产生的崩溃,并且导致了页断裂。什么是页断裂?简单来说就是 PG 的数据页只有部分被写入到了磁盘,页面的 crc32 检验值不正常。比如 PG 一个页面通常 8KB,而操作系统一次原子写入的页面可能为 4KB 或者更小的 512 B,这样就可能导致 1 个 8KB 的页面只有一部分写入成功,这种现象称之为页断裂。

产生页断裂后,为什么会影响后面的恢复过程?

PG 崩溃恢复时,以 checkpoint 检查点为起始点,在原始页面的基础上,应用 wal 日志。原始页面发生损坏,也就无法正常应用 wal 日志。因为应用 wal 日志时,会读取原始页面的数据,比如原始页面的的下一条记录的偏移值,原始页面发生损坏,读出来的数据已经不再可靠,基于损坏的原始页面恢复出来的数据,也不再可靠,即使强行做崩溃恢复,也可能会产生数据不一致。

看看官方文档的解释:

官方文档说【存储在 wal 中的行级变化数据不足以恢复这样一个页面】,如何理解这句话?来看一个 insert 产生的 wal 日志记录,里面包含两个重要信息。

第一个是这条 wal 记录属于哪个表空间文件以及哪个 page,对应的结构如下:

typedef struct RelFileNode { Oid spcNode; /* tablespace */ Oid dbNode; /* database */ Oid relNode; /* relation */ } RelFileNode; typedef uint32 BlockNumber;