半夜两点,手机屏幕亮了,是运维同事发来的消息,配着一张数据库连接报错的截图。这种事干过几年技术的人都不陌生,心跳漏一拍,脑子里瞬间闪过备份有没有做、最近一次全备是什么时候、binlog还剩下多少。别慌,先深呼吸,数据库这东西,绝大多数损坏都不是物理硬盘烧了那种终极灾难,绝大多数都是可以救回来的。我见过太多人一着急就乱点,结果把小问题搞成大事故。今天这篇,就是把那套救命的操作捋成三步,照着做,大概率能让你在天亮前把系统拉起来。

第一步,也是最重要的一步,把现有环境冻结住。怎么理解这个“冻结”?不是让你把服务器关机,而是停止一切写操作,包括业务写入、修复尝试、甚至重启数据库服务。为什么?因为数据库损坏很多时候是页损坏,就是磁盘上某个数据页的校验值对不上。此时如果继续写入,新数据可能覆盖掉那些还残留着旧数据的坏页,等于把原本可能通过工具挖出来的原始数据彻底抹平。我见过最惨烈的案例,是某公司数据库间歇性报错,DBA重启了一下,结果从InnoDB崩溃恢复变成了文件系统级别的损坏,连数据目录都挂载不上了。所以,第一件事,用iptables或者防火墙把应用服务器的访问端口断掉,或者直接把数据库服务停掉,但注意,是正常停,不是kill -9,给InnoDB一个刷盘的机会。如果服务已经起不来了,那就别动数据目录里的任何文件,连ls都少执行,直接进入下一步。
第二步,判断损坏的层级和范围。这步决定了你后续用哪套方案。数据库损坏一般分三层:逻辑层、物理层、文件系统层。逻辑层损坏,比如表结构错乱、索引失效、某条记录内容被篡改,这种通常不影响整个库,用mysqldump导出或者用pt-table-checksum比对一下就能定位;物理层损坏,是数据页的checksum不对,MySQL的InnoDB引擎在读取时发现页头页尾校验不匹配,会直接报错,这种需要用innodbforcerecovery参数或者第三方工具来绕过;文件系统层损坏,是数据文件本身在磁盘上读不出来,比如坏道、目录项丢失,这种最麻烦,但也不是没救。怎么判断?看错误日志,MySQL的error log会明确告诉你“page [page id] is corrupted”还是“Can't read from file”还是“Table doesn't exist”。另外,如果你用的是云数据库,比如RDS,直接看控制台的事件通知,云厂商通常会把底层硬件异常也报告给你。记住,别在没搞清层级之前就乱跑修复命令,那跟医生没做CT就开刀一样危险。
第三步,分层施救,从最轻的招开始试。如果是逻辑层损坏,直接用mysqldump导出这张表,加上--force --ignore-errors参数,把能导出的行先导出来,坏掉的行跳过,再手工补数据。这一步成功率极高,因为逻辑损坏通常只影响少数几行。如果是物理层损坏,先把innodbforcerecovery从1试到6,每升一档重启一次数据库,看能否进入只读模式。注意,这个参数不是越高越好,6的时候InnoDB会跳过回滚段,可能导致事务不一致,所以一旦能进只读模式,立刻用mysqldump全量导出,导出后马上恢复正常参数。这里有个细节,mysqldump时加上--single-transaction和--no-tablespaces,前者避免锁表,后者跳过表空间文件读取,能减少因为坏页导致的导出中断。如果是文件系统层,先试试用ddrescue把整个数据目录镜像到一块新硬盘上,镜像过程中会自动跳过坏道,然后再对着镜像文件做恢复操作,这样至少不会让损坏扩大。别嫌这些步骤啰嗦,每一条都是拿真金白银买来的教训。
实际操作时,有个工具必须提:Percona Data Recovery Tool for InnoDB,简称DRI。这工具能直接扫描数据文件里的行记录,把那些没有损坏的行提取出来。用法也不复杂,编译好后执行,它会输出一个SQL文件。但注意,这个工具只支持InnoDB,而且需要表结构定义文件,如果表结构也坏了,你得先想办法从ibd文件里解析出表结构,或者用这个工具(MySQL 8.0自带)从ibd文件里直接抽出表定义。我去年处理过一个客户案例,他们的订单表因为SSD掉电导致几十个页损坏,用DRI扫了两小时,救回了97%的数据,剩下3%是恰好落在坏页上的,谁都救不了,但业务方已经谢天谢地了。这工具唯一的缺点是慢,大表可能要跑一晚上,但凌晨出事的时候,有时间慢慢扫总比什么都拿不回来强。
还有一类特殊情况,就是你没开binlog,也没有全备,那就只剩一条路:找数据恢复公司做磁盘级别的数据抢救。这种就别自己折腾了,把硬盘拆下来,用干净静电袋装好,顺丰加急寄过去。价格不便宜,按恢复的数据量计费,动辄几万块,但比起公司破产,这笔钱花得值。不过我得说句实话,这类服务成功率高不高,取决于损坏类型。如果是误删除,恢复概率很高;如果是物理坏道,能救回部分数据;如果是被覆盖写入过,基本没戏。所以,别把备份当儿戏。
说到备份,其实最理想的恢复手段是“不需要恢复”。我知道这话听起来像废话,但你看那些数据库损坏的案例,能快速恢复的,都是因为备份策略得当。我见过最靠谱的做法是:每天全备,每小时binlog增量,加上定期做恢复演练。恢复演练这事,99%的公司都没做过,但真出事的时候,你会发现演练过的人手不抖、心不慌,按流程走,两小时就拉起来了。没演练过的,边查文档边试,四小时过去了还在纠结参数。所以,我每次给企业做咨询,都会问一句:你上次成功恢复过数据库是什么时候?如果答案是“从来没试过”,那就先别管优化和扩容了,把备份恢复流程跑通再说。
回到文章开头的那个半夜电话。那次是我一个朋友,他们公司用的是MySQL 5.7,某天早上发现一张核心业务表查询报错“Table is marked as crashed”,这种是MyISAM引擎的经典故障,但他们的表是InnoDB,后来查日志发现是索引页损坏。他按照我上面说的流程,先停写,再用innodbforcerecovery=3启动,成功进入只读模式,然后用mysqldump导出那张表,再重建表结构导回数据。全程花了三个小时,业务中断但没丢数据。事后复盘,他们发现根因是SSD固件有bug,在异常断电时会导致部分页写入不完整。所以你看,数据库损坏这事,真的不是世界末日,它更像是一场火灾演习——你平时准备得越充分,真烧起来的时候,损失就越小。
再强调一遍那三步:冻结环境,判断层级,分层施救。每一步都有对应的工具和参数,别跳步,别着急。数据库这东西,你越慌它越烂,你冷静下来,它反而能给你留条活路。记住,大多数损坏都是可以恢复的,真正不可恢复的,是你在慌乱中做出的错误操作。下次再遇到数据库报错,先给自己泡杯茶,然后按这三步来——你会发现,数据库损坏这事儿,也就那么回事儿。


