半夜两点,手机突然响了。电话那头是同事带着哭腔的声音:“我把生产库的表清空了,能恢复吗?”这种场景,干过运维的人都不陌生。MySQL数据误删这事,说大不大说小不小,关键是看你会不会恢复。今天就把这些年攒下的恢复经验摊开说,从最简单的备份回滚到最硬核的binlog追回,能救一个是一个。

先说最常规的操作——备份恢复。很多人一听“备份”就撇嘴,觉得老生常谈,但真出事的时候,有备份的人能笑醒,没备份的人只能哭。如果你有mysqldump或者物理备份,恢复就是一条命令的事。比如用mysqldump做的逻辑备份,直接,全量恢复干净利落。但这里有个坑,备份文件可能不是最新的,比如昨天半夜备份的,今天下午删的数据,那恢复回来还是缺一段。所以别急着收工,先确认备份时间点和误删时间点之间差了多少数据,心里有个数。
备份不够新怎么办?这时候就得翻binlog了。MySQL的binlog就像飞机的黑匣子,记录着每一次数据变更。前提是你提前开启了binlog,并且设置了合理的过期时间。查看binlog是否开启,一句,如果是ON,恭喜你,有戏。恢复思路是:先用全量备份把库恢复到备份时刻的状态,然后从备份时间点开始,重放binlog里那些“不该发生的操作”之外的所有变更。具体操作是,把这段时间的增量变更补回去。但记住,如果你误删的操作也在这段时间里,得先定位到那一条DELETE语句的位置,用在它之前停住,或者干脆跳过那条语句。
要是连binlog都没开,或者binlog过期被清掉了,那才叫真头疼。这时候还有一招——利用MySQL的undo表空间。InnoDB引擎的undo日志会记录事务回滚需要的数据,但如果事务已经提交,undo里的旧版本数据随时可能被purge线程清理掉。所以这招的时效性很强,得在误删后尽快操作。思路是:如果误删发生在最近几分钟内,且事务没有提交,直接就能救回来。但大多数情况是已经提交了,这时候可以试试用或者闪回查询,前提是MySQL版本支持,比如8.0.23以上才有语法。更土的办法是,把整个数据目录做个快照,然后用工具解析ibd文件里的残留数据页,但这活儿太专业,普通人玩不转。
还有一个容易被忽略的恢复途径——从库。如果你搭建了主从复制,而且从库还没同步到误删的那条语句,那从库上的数据还是完整的。赶紧把从库提升为主库,或者从从库把数据导出来再导回主库。但这里有个细节,很多主从架构用的是异步复制,从库可能已经追上了误删操作,那就白高兴一场。所以平时监控主从延迟很重要,延迟越大,恢复的窗口就越大。另外,有些团队做了延迟从库,专门设置一个比主库慢半小时的从库节点,就是为了应对这种误删场景,这个思路值得借鉴。
聊完技术方案,得说说实操时的几个救命细节。第一,误删后立刻把数据库设为只读,或者干脆停掉应用服务,防止新的写入把旧数据覆盖掉。尤其InnoDB的purge线程很勤快,你多等一分钟,旧版本数据就多一分被清理的风险。第二,别急着重启MySQL实例,有些恢复手段依赖内存里的状态,一重启可能就真没了。第三,你在执行任何恢复操作之前,先给当前的数据目录整个复制一份,万一恢复操作搞砸了,还有回退的余地。这就像做手术前先拍CT,别直接开刀。
恢复过程中还有个常见误区,很多人喜欢直接在生产库上操作恢复,这非常危险。正确做法是,在另一台机器上搭一个临时实例,把备份和binlog恢复到临时实例上,确认数据没问题了,再导回生产。这样即使恢复过程中出现问题,也不会影响线上业务。另外,恢复完成后要做数据校验,不能只看行数对不对,要抽查几条关键记录,看看字段值是否合理。曾经有人恢复了数据,但binlog重放时少了个事务,导致外键约束全部失效,业务跑起来全是脏数据,这种事故比误删还难收拾。
说到底,数据恢复这事儿,七分靠制度,三分靠技术。每次误删之后,除了把数据找回来,更应该反思几个问题:为什么没有开启binlog?为什么备份策略不够密集?为什么删除操作没有走审批流程?我见过很多团队,出事了忙活一整夜,恢复完就散了,下次接着踩同一个坑。真正的高手,会把这些恢复步骤写成文档,定期做演练,甚至会在测试库里故意删数据来检验恢复流程是否有效。毕竟,恢复方案靠不靠谱,只有演练过才知道,纸上谈兵没用。
回到最初那个半夜电话,后来那个同事的数据是怎么找回来的?是他记起来之前用mysqldump做过一次全量备份,但备份文件已经是一周前的了。我们先把备份恢复到临时库,然后用binlog把从备份时间点到误删时间点之间的更新全部重放了一遍,把误删的那几条DELETE语句跳过,数据完整度达到了99.7%。剩下那0.3%,是几个并发事务的边界情况,实在追不回来了。但那个同事已经谢天谢地了,他说以后每次删数据前,都会先问自己一句:“如果这表没了,我能恢复吗?”——这句话,也送给你。


