PostgreSQL 18 默认启用数据校验和

John Doe 一月 9, 2026

任何 PostgreSQL 数据库都可能存在隐性数据损坏,且这种损坏要到实际读取受损数据时才会被发现。

image

表或其他对象中的部分数据块受损可能由多种原因导致,即便现代存储硬件也绝非绝对可靠。使用pg_basebackup工具执行的二进制备份(这是 PostgreSQL 环境中非常常见的备份策略)会掩盖这类问题,因为该工具仅原样复制整个数据文件,不会对数据进行校验。随着 PostgreSQL 18 的发布,社区决定默认开启数据校验和,这是朝着及早发现此类故障迈出的重要一步。下面我们来探讨下 PostgreSQL 如何实现校验和、如何处理校验和失败,以及如何在现有集群上启用校验和。

为何校验和至关重要

PostgreSQL 的表和索引以 8KB 的页面为单位存储。当页面写入磁盘时,PostgreSQL 会基于页面的每个字节(校验和字段本身除外)和页面的物理块地址计算出一个 16 位校验和,并将其存储在页头中。每次读取页面时,PostgreSQL 会重新计算校验和,并与存储的值进行比对。由于块地址是计算的一部分,该系统既能检测到页面内的位翻转,也能发现页面被写入错误位置的情况。

页面位于共享缓冲区时,校验和不会被维护,仅当页面从缓冲区高速缓存刷新到操作系统页面缓存时,才会计算校验和。因此,内存中页面的错误要到其被写入磁盘并再次读取时才能被检测到。PostgreSQL 使用快速的 FNV-1a 哈希算法(WAL 记录采用 CRC32C 算法),该算法进行过性能优化。在典型硬件环境下,计算校验和的开销极小,基准测试研究表明,常规工作负载下的性能损耗通常低于 2%。PostgreSQL 18 的发行说明承认其开销并非为零,但考虑到数据完整性的收益,社区还是接受了这个设计。

PostgreSQL 18 的变更

PostgreSQL 18 默认启用数据校验和。在早期版本中,initdb命令需要通过--data-checksums参数才能开启该功能。新版本的发行说明在不兼容性部分明确列出了这一变更:“将initdb的默认设置改为启用数据校验和 …… 可通过新增的--no-data-checksums选项禁用校验和”。

对于数据库管理员(DBA)而言,这一默认变更存在两个重要影响:

  1. 集群升级需匹配校验和设置(PostgreSQL 18 发行说明中已明确提及)。通过pg_upgrade升级时,源集群和目标集群必须同时启用或禁用校验和。若要从关闭校验和的旧版本集群升级,需通过--no-data-checksums初始化新集群,或先在旧集群上启用校验和。
  2. 故障监控统计时,PostgreSQL 的pg_stat_database表中已包含两个字段:checksum_failures(统计校验和失败的页面数量)和checksum_last_failure(记录最近一次失败的时间戳)。这些指标可用于监控集群中所有数据库的损坏事件并发出告警。

要查看集群是否启用了数据校验和,可通过以下命令查看只读系统变量data_checksumsSHOW data_checksums;。若结果为 “ON”,表示数据页校验和已激活。

使用 pg_checksums 启用和关闭校验和

校验和是集群级别的属性,服务器运行时无法切换其状态。PostgreSQL 提供了pg_checksums工具,用于校验、启用或关闭校验和。文档中的核心要点如下:

  • 运行pg_checksums前,集群必须干净关闭;
  • 校验校验和(--check):扫描PGDATA目录下的所有文件,若发现不匹配项,返回非零退出码;
  • 启用校验和(--enable):重写每个关系块,更新磁盘上的校验和字段;关闭校验和(--disable):仅更新控制文件,不重写页面;
  • 辅助选项:--progress显示进度、--no-sync跳过修改后的fsync操作、--filenode将校验范围限定为特定关系;
  • 对于大型集群或复制集群,启用校验和可能耗时较长;所有备库必须停止或重建,以确保所有节点的校验和状态一致(文档中已明确提及)。

升级策略

若要从关闭校验和的版本低于 18 的集群升级,有两种选择:

  1. 在新集群上关闭校验和:执行initdb时添加--no-data-checksums参数,使pg_upgrade允许迁移。升级完成后,可通过pg_checksums --enable离线启用校验和;
  2. 先在旧集群上启用校验和:关闭旧服务器,执行pg_checksums --enable -D $PGDATA(若使用流式复制,需在所有节点上执行),然后启动服务器并通过SHOW data_checksums验证状态。初始化 PostgreSQL 18 时,会继承该启用状态。

校验和失败的处理

当 PostgreSQL 检测到校验和不匹配时,会发出警告并抛出错误。有两个仅开发者可用的 GUC 参数(全局配置参数)控制后续行为,正常运行时绝不应启用,但 DBA 可用于数据恢复:

  1. ignore_checksum_failure:默认关闭,关闭时服务器会在首次出现校验和错误时中止当前事务;设置为开启后,会记录警告并继续处理,允许查询跳过受损块。该选项可能掩盖损坏、导致崩溃或返回错误数据,仅超级用户可修改;
  2. zero_damaged_pages:检测到受损页头或校验和时,设置为开启会使 PostgreSQL 将内存中整个 8KB 页面置零后继续处理,置零后的页面随后会写入磁盘,导致该页面上的所有元组被销毁。仅当备份和备库均不可用时,才可使用该选项。关闭该参数不会恢复数据,仅影响后续受损页面的处理方式。

下面简化的示例展示了这些设置的效果:

-- 当 ignore_checksum_failure=off 时,查询在首次错误时停止:
SELECT * FROM pg_toast.pg_toast_17453;
WARNING:  page verification failed, calculated checksum 19601 but expected 152
ERROR:    invalid page in block 0 of relation base/16384/16402

-- 当 ignore_checksum_failure=on 时,服务器记录警告并继续扫描,直至找到有效数据:
SET ignore_checksum_failure = ON;
SELECT * FROM pg_toast.pg_toast_17453;
WARNING:  page verification failed, calculated checksum 29668 but expected 57724
WARNING:  page verification failed, calculated checksum 63113 but expected 3172
WARNING:  page verification failed, calculated checksum 59128 but expected 3155

-- 当 zero_damaged_pages=on 时,无效页面会被置零而非抛出错误,查询继续执行但页面数据丢失:
SET zero_damaged_pages = ON;
SELECT * FROM pg_toast.pg_toast_17453;
WARNING:  page verification failed, calculated checksum 29668 but expected 57724
WARNING:  invalid page in block 204 of relation base/16384/17464; zeroing out page
WARNING:  page verification failed, calculated checksum 63113 but expected 3172
WARNING:  invalid page in block 222 of relation base/16384/17464; zeroing out page

在内部实现中,当验证失败且设置了READ_BUFFERS_ZERO_ON_ERROR标志时,缓冲区管理器会通过调用memset()函数将8KB页面置零;若未设置该标志,则会将缓冲区标记为无效并抛出错误。需明确的是,校验和以及ignore_checksum_failurezero_damaged_pages参数均无法修复受损数据块,这些选项仅为挽救剩余数据的最后手段,使用后必然导致数据丢失。页面在内存中被置零后,即便将zero_damaged_pages恢复为OFF,其之前的受损内容也无法恢复,必须从已知完好的备份或备库中还原原始有效数据。

与自动清理(Autovacuum)的交互

自动清理进程在扫描表时可能会遇到受损页面。由于自动置零页面可能会静默销毁数据,自动清理启动器会强制为其工作进程禁用zero_damaged_pages参数。源代码中通过调用SetConfigOption函数将zero_damaged_pages设置为false,并附带注释说明该危险选项绝不应以非交互方式启用。这样一来,仅当用户直接操作受损页面时,才会对其进行置零处理。

为何应采用校验和

未启用校验和的数据库遭遇数据损坏时,可能会陷入更棘手的局面。无校验和情况下,仅能检测到页头明显受损的页面并将其置零。以下 PostgreSQL 代码中的测试片段可说明,无校验和时即便这种检测也并非易事——注释如下:

/*
 * 以下检查无法证明页头正确,仅能确认其看似正常可放入缓冲区池。
 * 该块后续使用过程中仍可能暴露问题,这也是提供校验和选项的原因。
 */
if ((p->pd_flags & ~PD_VALID_FLAG_BITS) == 0 &&
    p->pd_lower <= p->pd_upper &&
    p->pd_upper <= p->pd_special &&
    p->pd_special <= BLCKSZ &&
    p->pd_special == MAXALIGN(p->pd_special))
    header_sane = true;

if (header_sane && !checksum_failure)
    return true;

这段代码主要检测页头中的关键值是否符合预期的数值关系。以下是健康数据页的示例:

SELECT * FROM page_header(get_raw_page('pg_toast.pg_toast_32840', 100));
    lsn     | checksum | flags | lower | upper | special | pagesize | version | prune_xid
------------+----------+-------+-------+-------+---------+----------+---------+-----------
 0/2B2FCD68 |        0 |     4 |    40 |    64 |    8192 |     8192 |       4 |         0
(1 row)

因此,仅当页头的标志位、lowerupperspecial和/或pagesize明显受损时,才能被安全检测为损坏,此时会抛出如下错误信息:

ERROR: XX001-invalid page in block 578 of relation base/16384/28751

且仅有这类页面会被置零。但如果页头完好(或至少通过上述测试),则可能出现多种不同错误,这些错误要么由受损的条目 ID 数组导致,要么由元组中的受损系统列引发:

  • 受损的条目 ID 数组会包含错误的元组起始偏移量和元组长度,这些损坏的数值可能导致无效的内存分配请求,甚至导致读取数据的会话崩溃:
    ERROR:  invalid memory alloc request size 18446744073709551594
    DEBUG:  server process (PID 76) was terminated by signal 11: Segmentation fault
    
  • 若条目 ID 数组值完好,但元组受损,通常会出现多种错误,提示多版本并发控制(MVCC)系统中用于可见性检查的关键系统列xminxmax包含无效值:
    58P01 - could not access status of transaction 3047172894
    XX000 - MultiXactId 1074710815 has not been created yet -- apparent wraparound
    WARNING:  Concurrent insert in progress within table "test_table_bytea"
    

遇到这些错误时,若没有可用于数据恢复的可靠备份,可能需要进行复杂且耗时的手动修复和数据挽救。这些描述充分表明,启用数据校验和对 PostgreSQL 社区而言是一项至关重要的变更。

结论

PostgreSQL 18 决定默认启用数据页校验和,这一决策基于实践经验,校验和的性能影响极小,而收益却极为显著。校验和能检测多种隐性损坏事件,便于在硬件出现故障时快速诊断问题;即便在无法获取可靠备份的情况下,也能更快速、更轻松地挽救有效数据。