PostgreSQL 在初始化数据目录时可以通过 initdb -k 开启数据页校验和。开启后,每个数据页头部都会保存一个校验值,数据库每次从磁盘读取页面到共享缓冲区时,都会重新计算校验值并与页面头部的记录进行比对。如果两者不一致,说明这个页面在写入后发生了某种损坏,可能是磁盘介质老化、文件系统错误、内存位翻转或者非正常关机导致的部分写入。

正常情况下,PostgreSQL 会直接抛出类似 invalid page in block 的错误,拒绝把可疑数据交给查询层。这个设计是为了保证数据正确性,避免一个坏页污染整个业务结果。但有时候数据库里只有个别页面损坏,其他大量数据仍然完好,尤其是故障发生后需要紧急抽出关键数据时,直接报错会让恢复工作变得非常被动。ignore_checksum_failure 就是为了这种场景提供的一个开关。
需要说明的是,这个参数只对从数据文件读取的堆页面等关系数据页生效,并不等于数据库放弃所有校验。WAL 记录、索引元信息等仍然有自己的保护机制。而且参数开启后,PostgreSQL 并不是不检查,而是检查失败后不再中断查询,改为记录一条 WARNING 日志,继续读取该页面。页面内容可能已经部分损坏,也可能只是校验值本身被破坏而业务数据并没有实质变化,这部分不确定性正是使用该参数时必须清醒认识到的风险。
数据页校验和与 ignore_checksum_failure 的底层逻辑
PostgreSQL 的页面校验和并不是一种加密完整性校验,它的主要目标是检测页面在写入磁盘之后是否发生过意外变化。校验值存储在页面头的 pd_checksum 字段中,长度是两个字节。读页面时,PostgreSQL 会从磁盘读入 8192 字节的页面,根据页面内容重新计算出一个校验值,再与 pd_checksum 比较。如果不匹配,就代表页面可能已经损坏。
当 ignore_checksum_failure 设置为 off 时,任何校验不匹配都会导致查询报错,错误信息通常包含数据库 OID、表 OID 和损坏页号,类似 invalid page in block 123 of relation base/16385/16403。这些信息对定位坏页非常有价值。当参数设置为 on 时,同样的场景下错误不会抛给客户端,而是写入服务器日志,查询继续执行。查询结果可能是正确的,也可能是错误的,还可能是部分正确、部分乱码,完全取决于页面损坏的具体位置和程度。
这里有一个容易混淆的点:ignore_checksum_failure 忽略的是数据页读取时的校验失败,而不是忽略所有损坏。如果一个页面的头部元信息已经损坏到无法识别,或者页面完全不可读,数据库仍然可能报错。同样,如果损坏发生在 B-tree 索引页,忽略校验错误后执行索引扫描可能返回不正确的结果集,甚至导致后端进程崩溃。因此这个参数的最小化使用原则非常重要,不能把它当成修复手段,它只是允许你在已知风险下尽可能多地把数据捞出来。
如何启用并验证参数是否生效
ignore_checksum_failure 是一个布尔型参数,默认值为 off。它可以全局配置,也可以在会话级别临时打开,不需要重启数据库。最简单的方式是在当前会话中执行 SET ignore_checksum_failure = on;,这个修改只对当前连接有效,关闭连接后自动恢复默认值,很适合在恢复操作中控制影响范围。
-- 查看当前设置 SHOW ignore_checksum_failure; -- 会话级开启 SET ignore_checksum_failure = on; -- 会话级关闭 SET ignore_checksum_failure = off;
如果希望所有新连接都默认打开,可以在 postgresql.conf 中增加一行 ignore_checksum_failure = on,然后执行 SELECT pg_reload_conf(); 使配置生效。也可以使用 ALTER SYSTEM SET ignore_checksum_failure = on; 写入自动配置文件。但在生产环境不建议全局长期打开,因为任何一条查询都可能把损坏页面中的数据当成正常数据返回,而且不会在客户端留下任何明确提示,只有翻阅数据库日志才能发现问题。
验证参数生效最简单的方式是主动让一条查询命中一个已知损坏的页面。如果没有现成的坏页,可以在测试环境用 pg_checksums 工具关闭校验和,再手工修改某个数据文件中的几个字节,然后重新打开校验和并查询该表。开启参数前后观察错误信息和查询结果,可以很直观地理解参数的作用边界。测试时建议使用单独的实例,避免影响真实数据。
利用参数导出损坏表数据的完整流程
当数据库日志中出现 checksum 错误,首先要根据错误信息中的 relation 和 block 编号定位是哪张表、哪个页面出了问题。PostgreSQL 的错误信息通常会给出类似 base/16385/16403 的路径片段,其中 base 是表空间目录,16385 是数据库 OID,16403 是表的文件节点号。通过 pg_database 和 pg_class 可以反查出具体的数据库和表名。
-- 根据数据库 OID 查数据库名 SELECT datname FROM pg_database WHERE oid = 16385; -- 根据文件节点号查表 SELECT relname, relkind FROM pg_class WHERE relfilenode = 16403;
确认目标表后,不要直接在没有保护的情况下执行 SELECT * FROM damaged_table,因为如果查询计划走了索引,可能遇到损坏的索引页,导致进程崩溃或返回错误数据。推荐的做法是开启忽略校验错误的同时,强制使用顺序扫描,并且在事务中完成导出,避免中途退出留下半截文件。
BEGIN; SET LOCAL ignore_checksum_failure = on; SET LOCAL enable_indexscan = off; SET LOCAL enable_bitmapscan = off; SET LOCAL enable_indexonlyscan = off; COPY damaged_table TO '/tmp/damaged_table_dump.csv' WITH CSV HEADER; COMMIT;
上面的命令中,SET LOCAL 只对当前事务有效,事务结束自动恢复会话原有设置。COPY TO 需要超级用户权限,并且文件路径是数据库服务器本地路径。如果无法使用 COPY TO,也可以改用 SELECT 配合客户端工具逐批抽取,但要注意客户端可能因为遇到个别损坏页而中断。更稳妥的方式是按主键范围分段查询,避开已知损坏页对应的数据区间,逐步把健康数据导出来。
导出完成后,应该立即在事务外重新关闭 ignore_checksum_failure,然后对导出的数据做完整性核对,比如统计行数、检查关键字段是否为空、与备份或业务预期进行对照。如果目标表有唯一约束或外键关系,还要在目标库中导入后执行约束校验,以发现可能因静默损坏产生的非法数据。恢复完成后,原表大概率需要从备份重建或者通过逻辑导入的方式替换。
风险边界、参数对比与恢复策略选择
把 ignore_checksum_failure 打开以后,最危险的并不是数据库报错,而是数据库不报错。损坏的页面可能正好命中了某条 UPDATE 的过滤条件,导致业务逻辑在错误的数据基础上继续写入其他表,把损坏扩散出去。由于查询层完全无感知,这种错误可能在几天甚至几周后才被发现,届时定位根因会非常困难。所以参数只应在明确的恢复窗口内使用,用完之后必须关闭,并且使用期间最好把相关表设置为只读,避免产生新的写入。
PostgreSQL 还提供了另一个相关参数 zero_damaged_pages。它的行为与 ignore_checksum_failure 不同:当检测到页面损坏时,zero_damaged_pages 会直接把整个损坏页面视为全零页返回,查询不会失败,但该页中原本存在的行都会被当作不存在。这在某些情况下可能比返回不确定的错误数据更危险,因为业务上会表现为数据凭空消失。两个参数可以叠加使用,但叠加后风险会叠加,不建议在同一个恢复流程中同时打开,除非已经非常清楚损坏页面的具体分布和业务影响。
更稳妥的恢复策略优先级应当是:先尝试从备份和 WAL 归档做 PITR 恢复;如果没有可用备份,再考虑在副本上使用 ignore_checksum_failure 抽取数据;最后才在主库上临时启用该参数。日常运维中,定期使用 pg_checksums --check 对集群做全量校验,可以提前发现页面级损坏,而不是等到查询报错。对于关键库,还应该结合 pageinspect 扩展的 page_checksum 函数对可疑页面做单独验证。无论如何,参数只是应急工具,真正的数据安全保障仍然来自备份、归档和定期校验。
PostgreSQLignore_checksum_failure数据页校验和修改时间:2026-10-04 07:20:07