前言

前言

前几天,某位读者十万火急地找到我,说开发误删了某些数据,需要找回,并且更尴尬的是,基础备份也没有,只有 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

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

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!这么好的工具值得更多人知道。

最后,记得做好备份。