前言

前几天,某位读者十万火急地找到我,说开发误删了某些数据,需要找回,并且更尴尬的是,基础备份也没有,只有 WAL 归档。最终兜兜转转,经过几天的努力,终于在今天下午,读者传来捷报,数据抢救成功!!听到这个消息,我也甚是激动。

借着这个真实案例,再次和各位聊聊,在 PostgreSQL 中,数据误删了怎么办?怎么预防?真误删了怎么抢救?

与时间赛跑

打开网易新闻 查看更多图片

由于 MVCC 的实现方式就像信用卡一样,删除、更新和回滚留下的"债务"需要去偿还,偿还的动作便是由专门的 VACUUM 进程来做的,因此,数据被删了可以理解为仅仅是打了个标记,然后通过可见性规则来控制数据的可见性。因此,如果你发现误删了数据,或者更新错了数据,第一时间你要做的事情便是关闭表级的 autovacuum,alter table test set (autovacuum_enabled = off),再来谈后续的恢复动作,当然你也不要去做什么 vacuumdb 或者 vacuum + 表名之类的行为去手动删除数据。

然后你就可以通过 pg_dirtyread、pg_recovery 之类的工具,读出还未被 vacuum 清理掉的数据,具体使用方式就不再过多叙述。

pg_recovery 与 pg_dirtyread 类似,但是使用更灵活。目前的版本中默认只返回需要找回的数据。pg_recovery 的目标致力于数据的找回,而不仅仅是读取 dead 元组,在后续的版本中,增加一些辅助数据找回的调试信息,来帮助用户更快的在众多数据中找到自己需要找回的数据。

那么怎么判断是否还可以使用如上工具进行抢救呢?很简单,pg_stat_all_tables.last_vacuum 和 last_autovacuum 记录了上次清理和自动清理的时间,如果发现这两个字段的时间晚于数据误删的数据,那么上面的方式就可以不用尝试了,类似的还有 pg_resetwal、pg_filedump,都是基于相同的原理来找回误删的数据。

PITR

前面也提到了,这位读者只有 WAL 归档,以及主从流复制 (由于库太大,没有基础备份),那么有没有可能进行 PITR,恢复到指定点位呢?理论上,使用 pg_waldump 找到删除动作的 xid/lsn 等,然后基于可用的数据恢复一下行不行?比如把备库拆了,基于这份数据去恢复一下?可惜的是,不行。

我在 PITR 文章中也介绍过相关原理

  1. 如下示意图,第一次恢复到 12:40,我想基于这个实例继续再恢复一次,恢复到 12:45,这样操作是否可行?
  2. 又或者我第一次恢复到了 12:45,结果发现恢复多了,第二次又想往前恢复到 12:40,这样操作是否可行?

打开网易新闻 查看更多图片

答案是,第一种情况可以,第二种情况不行,当需要归档恢复时,数据库必须确保达到 minRecoveryPoint 这个位点。数据库在归档恢复过程中,minRecoveryPoint 会被更新为最新已经被刷到磁盘的 LSN,每次数据库启动时必须已经回放了该位置的 XLOG 日志记录,对应的是控制文件里的 Minimum recovery ending location。因此,使用"后面"的实例来往前恢复是不行的,Maybe 你可以 hack 一下控制文件?但是我想不会仅仅改一下 LSN 这么简单,其次 PITR 还有个关键的 REDO LSN,即从哪个点位开始进行恢复,又回到了之前的例子,你要找到 LSN,并创建 backup_label,这同样也需要对 PITR 机制很了解才行。

延迟备库

另外一个方式,自然是延迟备库了。延迟备库论原理来说其实不算是闪回,但是有时或许也能救你一命,比如 drop table,pg_dirtyread 无法实现 DDL 的找回 (基于 MVCC 的原理基本都只能找回 DML 数据),不过 drop column 可以找回。

顾名思义,延迟备库就是设置备库延迟 replay WAL 的时间 (recovery_min_apply_delay 参数控制),而备库依然及时接收主库发送的日志流,只是不是一接收到后就立即应用,而是等待此参数设置的值再进行应用。所以如果你在主机执行了一个没有条件筛选的 DELETE 操作,幡然醒悟。这是备库还没有回放,可以赶紧采取紧急措施补救数据。

但是延迟备库也有个很隐晦的问题:备库按部就班,原模原样复刻主库的操作。主库回滚我就回滚,主库删除我就删除,所以延迟备库的危害就不难理解了:

比如某个事务包含了 DDL 操作,那么这笔操作马上会在 HOT STANDBY 执行,锁在 HOT STANDBY 也同时被加载。但是当遇到这个事务的 commit record 时,由于设置了 recovery_min_apply_delay,这笔 record 被延迟执行,这个锁也会延迟到这笔 record 被 apply 为止。在此延迟时间段内,对这个被执行 DDL 的表的 QUERY 都会被堵塞。

可惜的是,这位读者的确设置了延迟备库,但是尴尬的是,只有 5 分钟... 这不是架构杂耍么?以下图片来自冯董的高可用容灾最佳实践。

打开网易新闻 查看更多图片

walminer

那么当你发现了数据被 vacuum 了怎么办?那就只能用最后一招——walminer了,walminer 支持多种功能,最为核心的功能是 wal2sql,即将 WAL 解析为具体的 SQL,这是类似 binlog2sql 的工具,但是前提是也需要 WAL,好在这位读者有误删期间的 WAL 归档。

因此,当我分析了这位读者的现象之后,发现只有walminer 可以抢救一下了

但是,在使用过程中又遇到了难题,walminer 是不支持 postgis 的!

打开网易新闻 查看更多图片

不过既然有需求,直接联系作者本人就行了,传成也很快回复了我,可喜的是,beta 版本也已经支持了 Postgis,并且理论上支持所有插件

打开网易新闻 查看更多图片

于是,吭哧吭哧,花了许久,在历经 N 个版本之后,终于,数据抢救了回来!隔着屏幕,我都能感受到这位读者的激动之情溢于言表。

后话

支持了插件体系之后,walminer 进入了一个全盛状态,如其 slogan 所说:walminer在手,天下我有。

感谢作者提供这么好用的工具,弥补了日志解析这块生态的空白。开源创作不易,也希望各位读者多多支持作者, https://gitee.com/movead/XLogMiner/wikis/walminer%20license

但是值得注意的是,以上讨论的都是对于 DML 的找回,对于 DDL 是很难找回的,虽然元信息可以找回,但数据已经不在磁盘上,理论上,只能采用恢复磁盘的办法抢救一下,但是不要抱有太大希望,实在不行,就搞个 event trigger 来预防吧。给 walminer 疯狂打 call!这么好的工具值得更多人知道。

最后,记得做好备份。