概要

【问题原因】服务器突然断电
【故障报告】数据库表结构损坏
【解决思路】进入强制恢复模式,库表及数据重建

故障发现

周末园区断电,周一启动数据库就直接报错了:

ERROR! The server quit without updating PID file (/var/lib/mysql/localhost.mobile.pid).

mysql decimal乘后精度 mysql decimal double_断电故障


干(chui)劲(tou)满(sang)满(qi)的去查看错误日志,尝试定位原因:

160822 12:57:35 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql
2016-08-22 12:57:35 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2016-08-22 12:57:35 0 [Note] /usr/sbin/mysqld (mysqld 5.6.25) starting as process 8966 ...
2016-08-22 12:57:35 8966 [Warning] Buffered warning: Changed limits: max_open_files: 1024 (requested 5000)

2016-08-22 12:57:35 8966 [Warning] Buffered warning: Changed limits: max_connections: 214 (requested 800)

2016-08-22 12:57:35 8966 [Note] Plugin 'FEDERATED' is disabled.
2016-08-22 12:57:35 8966 [Note] InnoDB: Using atomics to ref count buffer pool pages
2016-08-22 12:57:35 8966 [Note] InnoDB: The InnoDB memory heap is disabled
2016-08-22 12:57:35 8966 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
2016-08-22 12:57:35 8966 [Note] InnoDB: Memory barrier is not used
2016-08-22 12:57:35 8966 [Note] InnoDB: Compressed tables use zlib 1.2.3
2016-08-22 12:57:35 8966 [Note] InnoDB: Using Linux native AIO
2016-08-22 12:57:35 8966 [Note] InnoDB: Using CPU crc32 instructions
2016-08-22 12:57:35 8966 [Note] InnoDB: Initializing buffer pool, size = 128.0M
2016-08-22 12:57:35 8966 [Note] InnoDB: Completed initialization of buffer pool
2016-08-22 12:57:35 8966 [Note] InnoDB: Highest supported file format is Barracuda.
========================
打入日志内部的奸细,提醒重点
========================
---
2016-08-22 12:57:35 8966 [Note] InnoDB: The log sequence numbers 583029738 and 583029738 in ibdata files do not match the log sequence number 587038442 in the ib_logfiles!
2016-08-22 12:57:35 8966 [Note] InnoDB: Database was not shutdown normally!
2016-08-22 12:57:35 8966 [Note] InnoDB: Starting crash recovery.
2016-08-22 12:57:35 8966 [Note] InnoDB: Reading tablespace information from the .ibd files...
2016-08-22 12:57:35 8966 [ERROR] InnoDB: Attempted to open a previously opened tablespace. Previous tablespace mysql/slave_master_info uses space ID: 4 at filepath: ./mysql/slave_master_info.ibd. Cannot open tablespace acc/ac_acc_link_11 which uses space ID: 4 at filepath: ./acc/ac_acc_link_11.ibd
---
2016-08-22 12:57:35 7f2a6cfcf720  InnoDB: Operating system error number 2 in a file operation.
InnoDB: The error means the system cannot find the path specified.
InnoDB: If you are installing InnoDB, remember that you must create
InnoDB: directories yourself, InnoDB does not create them.
========================
打入日志内部的奸细,提醒重点
========================
---
InnoDB: Error: could not open single-table tablespace file ./acc/ac_acc_link_11.ibd
InnoDB: We do not continue the crash recovery, because the table may become
InnoDB: corrupt if we cannot apply the log records in the InnoDB log to it.
InnoDB: To fix the problem and start mysqld:
---
InnoDB: 1) If there is a permission problem in the file and mysqld cannot
InnoDB: open the file, you should modify the permissions.
InnoDB: 2) If the table is not needed, or you can restore it from a backup,
InnoDB: then you can remove the .ibd file, and InnoDB will do a normal
InnoDB: crash recovery and ignore that table.
InnoDB: 3) If the file system or the disk is broken, and you cannot remove
InnoDB: the .ibd file, you can set innodb_force_recovery > 0 in my.cnf
InnoDB: and force InnoDB to continue crash recovery here.
160822 12:57:35 mysqld_safe mysqld from pid file /var/lib/mysql/localhost.mobile.pid ende

上面标记的log,明确表示是非正常关机(InnoDB: Database was not shutdown normally!)导致表结构损坏了,并且在最后给出了三种修复建议:

  1. 权限问题。我的文件无此类问题,略过该方案;
  2. 跳过当前表恢复。我出错的表比较重要,全额无法通过备份恢复,所以该方案也不合适
  3. 调整强制恢复级别,强制修复表结构。我后续的处理,使用了选择了该方案。

断电时往往数据结构都还是好的,只是mysql事务未完成,有坏的数据在这里,所以有错误。 理论上只要修复表结构、去除坏的数据,mysql就可以正常恢复了。

处理故障

如上所述,权衡之后,我采用了强制启动+导出数据重建的方式。

调整强制恢复级别

找到配置进入编辑模式:# vim /etc/my.cnf 配置文件中调整级别:

## ignore previous roll
innodb_force_recovery=6

因为之前也遇到过类似问题,低级别恢复失败了。所以我这里直接使用级别6:跳过启动检测直接进行库表重建。
如果是其他情况的启动异常,具体问题分析后再确定采用哪个级别。

innodb_force_recovery参数说明:

影响整个InnoDB存储引擎的恢复状况,默认值为0,表示当需要恢复时执行所有的恢复操作。 当不能进行有效的恢复操作时,MySQL有可能无法启动,并记录下错误日志。

innodb_force_recovery可以设置为1-6,大的数字包含前面所有数字的影响。 当设置参数值大于0后,可以对表进行select/create/drop操作,但insert/update/delete这类操作是不允许的。

  • 1 (SRV_FORCE_IGNORE_CORRUPT):忽略检查到的corrupt页
  • 2 (SRV_FORCE_NO_BACKGROUND):阻止主线程的运行,如主线程需要执行full purge操作,会导致crash
  • 3 (SRV_FORCE_NO_TRX_UNDO):不执行事务回滚操作。
  • 4 (SRV_FORCE_NO_IBUF_MERGE):不执行插入缓冲的合并操作。
  • 5 (SRV_FORCE_NO_UNDO_LOG_SCAN):不查看重做日志,InnoDB存储引擎会将未提交的事务视为已提交。
  • 6 (SRV_FORCE_NO_LOG_REDO):不执行前滚的操作。

启动MySQL

配置改好后,就可以启动了。

启动方式选择常用的即可,但不能重复执行,否则提示进程已存在。

  • /etc/init.d/mysql start
  • mysqld_safe --user=mysql
  • mysql decimal乘后精度 mysql decimal double_断电故障_02


正常启动后,注意此时我们只是跳过了启动检查,表数据仍然是坏的。新的数据读写操作进来,仍然可能会出错,甚至导致脏数据滚雪球般放大。因此需要修复数据

修复数据文件

修复数据采用的是导出后重建。

导出数据

使用mysqldump命令导出:

mysqldump -uroot -p --default-character-set=utf8 acc > ./acc.sql

如果数据顺利导出了,可以直接查看下一节重建。

常见问题
提示密码过期
操作时出现了密码过期的提示:

mysql decimal乘后精度 mysql decimal double_数据损坏_03


尝试直接登录数据库后,没问题,但任何操作都会提示重设密码:


mysql decimal乘后精度 mysql decimal double_MySQL_04


原因暂时不清楚,毕竟只是断电,没有数据库升级或其他动作。我直接按提示重设密码(有明白原因的朋友可以留言指导下)


mysql decimal乘后精度 mysql decimal double_MySQL_05


能正常读了,接着执行备份:


执行命令:

mysqldump -uroot -p --default-character-set=utf8 acc > ./acc.sql

输入密码,备份成功。

损坏的是MySQL系统表,无法dump

参见常见问题 - 损坏的是系统表,无法dump,如何恢复

重建

到了这一步,就可以长出一口气了。
此时数据已备份好,最大的风险已经没有了。
为了保险起见,接下来的操作前,可以把数据文件备份掉。

删除目标数据库

尝试直接执行drop database方式删除,但报错了:

mysql decimal乘后精度 mysql decimal double_断电故障_06


提示表找不到:


mysql decimal乘后精度 mysql decimal double_断电故障_07


实际查看数据文件目录,文件是存在的。

考虑到数据已经备份,可以直接物理删除目录和里面的frm与ibd文件(谨慎起见,操作前最好备份一下数据dump文件)。

  • 找到mysql的data目录,找到目标库,删除整个目录(mysql无需停掉)
  • 再查看,数据库已经删除(比之前少了一个)
导入备份数据

重启MySQL进入正常读写模式:

将my.cnf中的innodb_force_recovery配置改为0或直接删掉(无该配置项时MySQL默认为0)

重启mysql:

mysqladmin -uroot -p shutdown;
mysqld_safe --user=mysql &;

然后重建数据库

create database [库名];
use [库名];
source [备份文件绝对路径,我的是acc.sql]

成功完成还原。
启动应用服务正常,数据库访问正常。
整个恢复过程基本完成。

常见问题
如未进入读写模式,直接重建了数据库,会出现大量类似报错:

mysql decimal乘后精度 mysql decimal double_mysql decimal乘后精度_08


明确提示是只读模式的问题。

这个是innodb_force_recovery大于等于4时,为避免文件损坏面积扩大,mysql采取的保护措施;

此时要进入读写模式,需要先删掉刚刚新建的库:


但会遇到报错:


mysql decimal乘后精度 mysql decimal double_MySQL_09


错误码39意味着acc目录下有文件;


查看mysql data目录,确认是刚刚创建失败的表,可以直接删除文件目录

总结

我这个案例只是傻瓜式操作,不一定适合所有故障场景,只是作为一种处理参考。
对于故障处理,还有几点补充:

  • 数据操作需慎之又慎,故障出现时一定要保留现场(禁止操作和新的读写污染),做好备份后再开始操作;
  • 操作过程中,发现我们的数据库日志实在太大了,好几个G~ 建议如果平时日志没有清理,顺便清理一下;
  • MySQL启动、应用运行看起来没问题。但后来查看数据库错误日志,发现其他库也存在一些问题。因此以后遇到类似情况,最好多检测几次日志,检查所有库,不要遗漏。

其他常见问题

找不到Mysql部署路径

像我这次操作的机器上,MySQL不是我部署的,所以查找路径也花费了一些时间。
尝试过whereis/netstat/iptables等命令,最后直接在mysql默认配置文件/etc/my.cnf中查找到路径
运维对部署路径治理,可以考虑有一个类似同构或固定的说明文档。

MyISAM引擎如何处理

上面所讲的数据库是InnoDB引擎,MyISAM有直接的表修复命令可用

损坏的是系统表,无法dump,如何恢复

这里使用的是mysql5.6的InnoDB数据库,5.6之前直接删除表重启mysql会自动重建

一般故障后,执行写操作就会报核心表错误,比如:

mysql decimal乘后精度 mysql decimal double_断电故障_10

常见的有五个系统表会一起出错:innodb_table_stats, innodb_index_stats, slave_master_info, slave_relay_log_info, slave_worker_info 因为表结构坏掉、又是mysql系统表,因此无法dump

【解决方法】
MySQL安装目录的share目录下、或者mysql的安装包的script目录下(我的是/usr/share/mysql/)下,有MySQL系统库的建表脚本。找到mysql_system_tables.sql文件,在其中可以找到上述几个表的建表语句。
然后就可以进行重建了。

  • 系统表重建也可能会遇到无法删除的情况,找到data目录下的mysql库,删除对应的文件即可
  • 导入系统表建表脚本时,提示表不存在

    需要清空cache,执行flush tables。然后再执行建表语句就不会报错了。
    执行完毕后,重启MySQL即可。