早上九点半,我正对着电脑啃三明治,手机突然震动。电话那头是前同事老李,声音发颤:“兄弟,公司官网挂了,数据库起不来了,老板在旁边盯着呢。”我问他怎么回事,他说昨晚跑了几个小时的批量更新,早上来发现MySQL服务起不来,错误日志刷了一屏InnoDB报错。这种场景我太熟了,干这行十年,每隔一阵子就得处理一次类似的求救。MySQL号称稳定,但真出问题的时候,往往是在你最不想出问题的时候。

先别慌,这是第一原则。数据库挂了,不等于数据没了。大多数情况下,文件还在磁盘上,只是MySQL进程起不来,或者某些表读不了。你越慌,越容易做蠢事——比如直接去动数据文件,或者反复重启服务,把本来能救的场景搞成彻底损坏。正确姿势是:先停掉所有对数据库的访问,包括应用服务器,然后备份整个数据目录。注意,是完整备份,不是只备份那几个看着像出问题的文件。这一步花了十分钟,但能保证你后面所有操作都有退路。
备份做完,下一步是看日志。MySQL的错误日志默认在数据目录下的文件里,或者你用查。日志里藏着线索,常见几种:,说明表数据页损坏;,多半是磁盘满了;,可能是权限或文件丢失。老李那个案例,日志里反复出现,指向某个表文件页损坏。这时候,别急着跑修复工具,先搞清楚损坏范围。
针对InnoDB表损坏,最常用的招数是参数。这个参数从1到6,数字越大,InnoDB启动时跳过的检查越多。做法是:在my.cnf或my.ini的段加上,然后尝试启动MySQL。如果还起不来,改成2、3,以此类推。但记住,这个参数是修数据用的,不是让你长期开着跑的。它能帮你把MySQL拉起来,让你有机会用把数据导出来。老李那次,我让他从1开始试,到3的时候MySQL起来了,但只读模式,正常业务写不进去。没关系,咱们目的是把数据捞出来。
数据捞出来,用导出整个库,或者只导出损坏的那些表。导出的SQL文件是纯文本,只要InnoDB能读到那些页,数据就能导出来。如果连导出都报错,说明损坏比较严重,这时候换个思路:直接跳过损坏的页。InnoDB有个参数,会跳过所有损坏页的读取,但代价是某些行的数据会丢失,或者整行变成NULL。这总比全丢强。导出成功后,把旧的数据库文件挪走,重新初始化一个新数据库,再把导出的SQL文件导入进去。整个过程就是:降级启动,导出,重建,导入。
但不是所有问题都出在InnoDB。有些情况下,MySQL起不来是因为目录下的文件权限不对,或者磁盘满了。老李那个案例,我让他先查磁盘空间,结果发现分区满了99%。批量更新产生的binlog把磁盘塞满了,MySQL写不了日志,直接拒绝启动。这种场景最简单,删掉一些旧的binlog,或者把binlog过期时间调短,释放空间后MySQL自己就起来了。所以,遇到MySQL起不来,第一件事永远是看磁盘,第二件事看错误日志,第三件事才是考虑数据损坏。
还有一种常见场景:MyISAM表损坏。MyISAM和InnoDB不一样,它没有崩溃恢复能力,断电或强制kill进程后,表文件可能损坏。症状是查询报错。修复工具是,操作方式:先停掉MySQL服务,然后在数据目录下执行,或者用命令(如果MySQL还能启动的话)。这个修复原理比较简单,重建索引、恢复数据文件。但MyISAM表如果损坏严重,可能修复后部分行丢失。所以,生产环境强烈建议用InnoDB,除非你明确知道自己在干什么。
再说回老李。我让他按步骤操作,从开始,到3时MySQL起来了。他赶紧用把核心业务库导出,大概花了20分钟。然后我让他把参数注释掉,删掉旧数据文件,重新初始化数据目录,再导入刚才的dump。整个过程用了不到一小时,官网恢复正常。他后来跟我说,当时手都在抖,以为要失业了。我告诉他,这活儿干多了就熟了,关键是别慌,按流程走。
说点预防的事。数据恢复不难,但最好永远别走到那一步。几个习惯能帮你省掉很多麻烦:第一,开binlog,就算数据库文件全毁了,binlog还能帮你恢复到最近一分钟的状态;第二,定期全量备份加增量备份,备份文件放另一台机器或者对象存储;第三,监控磁盘空间和数据库错误日志,报警比修复便宜多了;第四,生产环境别用MyISAM,别用启动。这些做到位,你遇到需要修复的场景概率会低很多,但真遇到了,你也知道该从哪下手。
数据库修复这事儿,说穿了就是两件事:一是别让情况恶化,二是把数据从坏环境里捞出来。本文这套流程,我已经用了无数遍,从几百人的小公司到几千万用户的大平台,底层逻辑都一样。你只要记住:备份先行,日志说话,参数降级,导出重建。下次再遇到MySQL起不来,先深呼吸,然后照着这个顺序排查。你会发现,数据恢复,真没那么难。


