如何修复损坏的数据库 PostgreSQL?
在PostgreSQL有可能因为硬件(磁盘控制器或某些内存)或bug等未知原因,导致数据文件的page corrupted损坏,只限于少数页面,有没有办法从部分损坏的 Postgres DB 中恢复数据?
错误日志
LOG: server process (PID xxxx) was terminated by signal 11
LOG: terminating any other active server processes
FATAL: the database system is in recovery mode
LOG: all server processes terminated; reinitializing
或查询出错
psql: FATAL: could not read block 0 in file "base/xxxx/xxxx": read only 0 of 8192 bytes.
--
ERROR: invalid page in block xxxxxx of relation base/xxxx/xxxx
尝试解决
1, 备份恢复
保底的方法是从备份文件中恢复(如果您有的话).但是在生产环境中执行 PITR 的成本通常很高,因为它涉及从基础备份创建完整的 postgres 并导致客户停机。
2, DD清空
如果报错中没有提示损坏的page, psql 中可用的选项之一是 FETCH_COUNT,如果设置了此选项,则会将 SELECT 查询包装在游标中,然后自动重复地从中获取指定数量的行。此选项主要用于允许 psql 显示大型查询的结果,而无需预先分配太多内存。
testdb=# \set FETCH_COUNT 1 testdb=# \pset pager off Pager usage is off. testdb=# SELECT ctid, * FROM anbob.test; ctid | generate_series -------+----------------- (0,1) | 0 ...
直到ctid 来告诉我们问题发生的页面,(正常读取的下一个),如果您要尝试任何操作,请关闭 Postgres ,并对数据目录进行文件级备份(cp)。
testdb=# select oid from pg_database where datname = 'testdb'; oid ------- 16393 (1 row) testdb=#* select relfilenode from pg_class where relname = 'test'; relfilenode ------------- 16394 (1 row) testdb=#* select *from anbob.test where ctid = '(10,1)'; -- try error testdb=#* \q anbob:~/$ dd if=data/base/16393/16394 bs=8192 skip=[报错的page位置] count=1 | hexdump -C | less
Note:
dd读取或使用pg_checksums -D$PGDATA –check检查坏块,识别出损坏的页面后,如果很明显无效数据覆盖了大部分或全部页面,则我们不太可能从中恢复任何行。我们最好的办法是将页面“清零”,以便 Postgres 跳过它,让我们从表中提取其余数据。我们可以使用 ‘dd’ 来查找表中损坏的块,并在其位置写出一个 8k 的零字节块。关闭 Postgres(只是为了确保它以后不会重新覆盖您的工作)并注意 conv=notrunc,这将防止 dd 截断表的其余部分。
dd if=/dev/zero of=data/base/16393/16394 bs=8192 seek=[报错的page位置] count=1 conv=notrunc anbob:~/$ dd if=data/base/16393/16394 bs=8192 skip=440 count=1 | hexdump -C 1+0 records in 1+0 records out 8192 bytes (8.2 kB) copied, 0.000147993 s, 55.4 MB/s 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| *
它现在是一个空的、未初始化的页面,Postgres 应该可以直接跳过,启动数据库,可以正常读取,但是从表中丢失了一些行数据。这当然不能替代真正的备份.
3,zero_damaged_pages跳过
像oracle数据库有event 10231、10233和DBMS_REPAIR.SKIP_CORRUPT_BLOCKS等手段,允许跳过坏块可以允许继续读取可用数据减少损失,在PostgreSQL中有一个特殊选项:zero_damaged_pages=on,您可以在 postgresql.conf 上使用它,它记录在这里。此选项将允许 pg_dump(或 pg_dump_all)不会在严重错误时停止并获取尽可能多的数据,但您将丢失无法读取的数据。
检测到损坏的页头通常会导致 PostgreSQL 报告错误,中止当前事务。将 zero_damaged_pages 设置为 on 会导致系统报告警告,将内存中的损坏页面清零并继续处理。 此行为将破坏数据,即损坏页面上的所有行。但是,它允许您忽略错误并从表中可能存在的任何未损坏的页面中检索行。如果由于硬件或软件错误而发生损坏,它对于恢复数据很有用。通常,除非您已经放弃从表的损坏页面中恢复数据的希望,否则不应将此选项设置为 on。清零的页面不会被强制写入磁盘,因此建议在再次关闭此参数之前重新创建表或索引。默认设置为 off,并且只能由超级用户更改。
4,pg_page_repair
还有一种有点像oracle 的ABMR,利用physical standby 自动修复损坏的block. 如《pg_page_repair:一种使用流/物理复制修复 postgres 中损坏页面的工具/扩展》提到的。
利用同步或异步备用数据库中未损坏的页面。建议的工具/扩展 pg_page_repair(我们称之为)可以从同步或异步备用数据库中获取未损坏的页面,并在主数据库中覆盖损坏的页面。
是的,确保WAL 完全重播且备用数据库是最新的,这样我们才能确保不会复制过时的页面,这将是一个挑战。
一个更简单的想法可能是pg_page_repair 可以等到备用数据库重播/赶上主数据库的刷新 LSN 后再获取未损坏的页面。这种方法的缺点是,如果复制滞后很大,pg_page_repair 会等待很长时间或无限期。众所周知,复制滞后是一个好的 postgres 解决方案将始终监控以使其保持较低水平的东西,如果属实,则保证 pg_page_repair 不会等待更长时间。
另一个想法可能是 pg_page_repair从备用服务器获取基页,并使用基页应用与损坏页面相关的所有 WAL 记录以获取未损坏的页面。这需要我们将重放逻辑从核心拉到 pg_page_repair,这并不容易。
因此,我们建议采用方法 1,但也欢迎讨论方法 2。我们假设本线程中提出的解决方案即使对于与索引相对应的页面也适用。
实现选择:pg_page_repair 可以获取损坏的页面信息(db id、rel id、块号等)或仅获取关系名称,然后使用 pg_checksums 自动找出损坏的页面,或者仅获取数据库名称,然后自动找出所有损坏的页面。它可以在线修复损坏的页面(只有损坏的表无法访问,服务器继续运行),或者在损坏的页面很多的情况下停机。
未来范围:pg_page_repair 可以集成到核心,以便postgres 自动修复页面而无需人工干预。
其他解决方案:我们确实考虑过一种方法,即工具可以从 WAL 获取 FPI,并重播到最新的 WAL 记录以修复页面。但可能会存在一些限制,例如 FPI 和相关 WAL在主/存档位置不可用。
从备库dd可以完成corrupted page 覆盖.或dd清空跳过zero块,或配置参数zero_damaged_pages=on自动把访问错误的块在内存级置zero。 checksum可以检查具体有哪些块是损坏. 另外注意操作过程的备份和日常数据库备份。
Rererences
https://www.tutorialdba.com/2017/10/how-to-repair-corrupt-database.html
对不起,这篇文章暂时关闭评论。