找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1482

积分

0

好友

194

主题
发表于 2026-2-10 19:41:40 | 查看: 29| 回复: 0

故障概述

最近处理了一起生产环境 MySQL 数据库主节点宕机且无法启动的紧急故障。该实例运行的是 MySQL 5.7 版本,采用一主两从的增强半同步复制架构。故障的直接表现是主节点异常宕机,在尝试重启时失败,触发了高可用切换。

故障现象与初步分析

首先,我们检查了 MySQL 的错误日志,这是排查启动失败问题的关键。日志显示了实例启动的完整过程,包括缓冲池初始化、表空间加载等,但在崩溃恢复阶段遇到了致命错误。

启动日志的关键部分如下:

2025-12-04T00:04:19.723007+08:00 [Note] InnoDB: Starting crash recovery.
2025-12-04T00:04:21.006287+08:00 [Note] InnoDB: Starting an apply batch of log records to the database...
InnoDB: Progress in percent: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
2025-12-04 00:04:21 0x400b9511f1c0 InnoDB: Assertion failure in file /data/mysql/5.7/src/storage/innodb/row/row0mysql.cc line 1000
InnoDB: Submit a detailed bug report to http://bugs.mysql.com.
InnoDB: If you get repeated assertion failures or crashes, even
InnoDB: immediately after the mysqld startup, there may be
InnoDB: corruption in the InnoDB tablespace. Please refer to
InnoDB: http://dev.mysql.com/doc/refman/5.7/en/forcing-innodb-recovery.html
InnoDB: Route forcing recovery.
16:04:12 UTC - mysqld got signal 6 :
This could be because you hit a bug. It is also possible that this binary
for one of the libraries it was linked against is corrupt, improperly built,
or misconfigured. This error can also be caused by malfunctioning hardware.

紧接着是一段崩溃的堆栈跟踪信息:

Attempting backtrace... You can use the following information to find out where mysqld died.
stack bottom = 0 thread_stack 0x40000
xxxxxxxxxxxxxxxxxx/bin/mysqld(my_print_stacktrace+0x2c)[0xdff85bc]
xxxxxxxxxxxxxxxxxx/bin/mysqld(handle_fatal_signal+0x40c)[0x74dbdc]
linux-vdso.so.1(__kernel_rt_sigreturn+0x0)[0x4002f48707c0]
/lib64/libc.so.6(gsignal+0xb0)[0x4002f50a64d0]
/lib64/libc.so.6(__abort+0x154)[0x4002f504878c]
xxxxxxxxxxxxxxxxxx/bin/mysqld[0x7245a0]
...
The manual page at `dev.mysql.com/doc/mysql/en/crashing.html` contains information that should help you find out what is causing the crash.
Writing a core file.

日志明确指出了问题:“there may be corruption in the InnoDB tablespace”,即 InnoDB 表空间可能存在损坏。服务器收到了信号 6 (SIGABRT),这是程序异常中止的典型信号。

为了定位损坏的具体位置,我们查看了实例宕机前一刻的日志,发现了更详细的错误信息:

2025-12-03T12:11:41.257928+00:00 [Note] InnoDB: Uncompressed page, stored checksum in field: 79879839, calculated checksums for field: crc32 79879839/2448898256, InnoDB: checksum mismatch, none 3735920559, a stored checksum in field: 79879839, calculated checksum for field: crc32 79879839/2448898256, none 3735920559, page LSN 145 38661252109, low 4 bytes of LSN at page end 3861252109...
2025-12-03T12:11:41.257928+00:00 [Note] InnoDB: Index id is 297 “PRIMARY in table: db1.t1”, index id: 146, page number:63107763[ to be written to data file. We intentionally crash the server to prevent corrupt data from ending up in data files.

这段日志非常关键。它表明在向数据文件写入数据时,InnoDB 检测到表 db1.t1 的 PRIMARY 索引(索引ID 297)在页号 63107763 上发生了校验和(checksum)不匹配。存储的校验和与计算出的校验和不一致,这被判定为页面损坏(Page Corruption)。为了保护数据的一致性,InnoDB 主动触发了服务器崩溃,以防止损坏的数据被写入持久化文件。

原因总结:根本原因是底层数据文件出现了坏块(Bad Block/Page Corruption),导致 InnoDB 存储引擎在崩溃恢复或正常读写时无法通过完整性校验,进而触发保护性宕机,且因损坏持续存在而无法正常启动。

解决方案:基于物理备份恢复

对于此类物理文件损坏,并且有可用备份和从库的场景,最可靠、最快的恢复方式是使用物理备份进行还原。我们的恢复步骤如下:

1. 从健康的从库获取备份

由于主库已损坏,我们选择从一个状态健康的从库上进行物理备份,并直接流式传输到待恢复的主机。

执行备份的命令如下:

xtrabackup --defaults-file=/xxxxx/my.cnf --backup --ftwrl-wait-threshold=10 --ftwrl-wait-query-type=all --ftwrl-wait-timeout=20 --kill-long-queries-timeout=60 --stream=xbsstream --parallel=4 --compress-threads=4 --user='xxxx' --password='xxxxx' --socket=xxxxx/my.sock --target-dir=/tmp --sshpass -p'xxxxx' ssh --oPreferredAuthentication=password -o StrictHostKeyChecking=no -o GSSAPIAuthentication=no teledb@xxxxxx ‘xbstream -x -C /backups’

这条命令的核心是利用 xtrabackup 进行热备份,并通过 --stream=xbsstream 将备份数据流直接通过 SSH 管道传输到目标机器的 /backups 目录。

2. 准备备份(Prepare)

在目标主机上,需要对流式传输过来的原始备份文件应用日志,使其达到一致状态。

innobackupex --apply-log /backup

命令执行输出中会包含关键的状态信息,例如:

251204 01:07:33 innobackupex: Starting the apply-log operation
IMPORTANT: Please check that the apply-log run completes successfully.
At the end of a successful apply-log run innobackupex prints “completed OK”.
...
strdbackup: 5.7.40 started, log sequence number 646252626653
strdbackup: starting shutdown with innodbd_fast_shutdown = 1
...
InnoDB: Shutdown completed; log sequence number 646258296874

看到 “completed OK” 以及日志序列号(LSN)推进,说明 prepare 阶段成功。

3. 复制回数据目录(Copy-Back)

将准备好的备份文件复制回 MySQL 的数据目录。

innobackupex --defaults-file=/xxxxx/my.cnf --user=root --copy-back /backup

此操作会逐个复制数据文件:

201204 01:09:01 [01] Copying undodb001 to /xxxxx/data/undodb001
201204 01:09:02 [01] ...done
...
201204 01:20:19 [01] Copying ./ibtmp1 to /xxxxx/data/ibtmp1
201204 01:20:19 [01] ...done
201204 01:20:19 completed OK!

4. 启动并重建主从关系

恢复文件后,调整文件属主并启动 MySQL 实例。由于是从从库备份恢复的,需要重建主从复制关系。

首先,查看备份中包含的 binlog 位置信息:

cat /smd/teledb_backup/xtrabackup_binlog_info

输出示例:mysql-bin.001471 14853225 e3ee263f-a309-11ee-82d2-8c2a8eed549d:1-80611942, e57fade1-a309-11ee-b5aa-8c2a8eed5475:1-56450773

然后在 MySQL 命令行中操作:

(root@localhost) [(none):mysql_8801.sock]> reset slave;
Query OK, 0 rows affected (0.03 sec)

(root@localhost) [(none):mysql_8801.sock]> show global variables like “%gtid%”;
-- 确认 gtid_mode 为 ON

(root@localhost) [(none):mysql_8801.sock]> set global gtid_purged=”e3ee263f-a309-11ee-82d2-8c2a8eed549d:1-80611942,e57fade1-a309-11ee-b5aa-8c2a8eed5475:1-56450773”;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [(none):mysql_8801.sock]> start slave;
Query OK, 0 rows affected (0.13 sec)

至此,通过物理备份完成恢复,主从复制重新建立。

应急方案:当没有可用备份时

如果没有可用的物理备份,情况会棘手得多。此时可以尝试让 MySQL 以恢复模式启动,然后尽可能多地逻辑导出数据。

1. 尝试强制恢复模式启动

在 MySQL 配置文件 my.cnf[mysqld] 部分添加以下参数,并尝试按顺序递增级别启动:

innodb_force_recovery = 1

innodb_force_recovery 参数有 6 个级别(1-6),级别越高,InnoDB 在启动时跳过的恢复操作越多,但也意味着数据丢失或不一致的风险越大。

各级别的功能与风险对比如下:

级别 功能 风险
1 (SRV_FORCE_IGNORE_CORRUPT) 忽略损坏页面 最低
2 (SRV_FORCE_NO_BACKGROUNDD) 禁止后台操作
3 (SRV_FORCE_NO_TRX_UNDO) 不执行事务回滚
4 (SRV_FORCE_NO_IBUF_MERGE) 禁止插入缓冲合并
5 (SRV_FORCE_NO_UNDO_LOG_SCAN) 不查看 UNDO 日志
6 (SRV_FORCE_NO_LOG_REDO) 不进行前滚恢复 最高

操作建议:从级别 1 开始尝试启动实例。如果启动失败,逐渐增加级别值(2, 3, …)直到实例能够启动。务必使用只读模式启动,并尽快导出数据,因为在该模式下数据文件可能处于不一致状态,写操作会导致进一步损坏。

2. 逻辑导出数据

一旦实例以恢复模式启动,立即使用 mysqldump 进行逻辑备份。

导出所有数据库:

mysqldump --all-databases --single-transaction --routines --triggers --events > /tmp/full_backup_$(date +%Y%m%d).sql

或者逐个数据库导出,排除系统库:

mysql -e “SHOW DATABASES;” | grep -v “Database” | grep -v “information_schema” | grep -v “performance_schema” | grep -v “sys” | while read db; do
    echo “导出数据库: $db”
    mysqldump --single-transaction “$db” > “/tmp/${db}_backup.sql” 2>/tmp/dump${db}.log
done

如果某个表损坏导致导出失败,可以排除该表,导出其他健康表:

mysql -e “SHOW TABLES FROM your_database” | grep -v “损坏的表名” | while read table; do
    mysqldump --single-transaction your_database “$table” > “/tmp/your_database_${table}.sql”
done

3. 在新实例中恢复

将成功导出的 SQL 文件导入到一个全新初始化的 MySQL 实例中。完成数据验证后,再将应用流量切换至新实例。

总结与建议

本次故障的核心是 InnoDB 数据页物理损坏。对于生产系统,防范此类风险远胜于补救:

  1. 定期备份与恢复演练:必须制定并严格执行物理备份策略,并定期进行恢复演练,验证备份的有效性。这是 运维 工作的生命线。
  2. 构建健壮的高可用架构:一主多从的架构在本案例中起到了关键作用,健康的从库成为了可靠的数据源。
  3. 监控与预警:需要监控数据库错误日志中的关键告警(如 checksum 错误),以便在问题恶化前提前介入。
  4. 硬件稳定性:虽然不常见,但存储介质(如磁盘、SSD)故障是导致数据坏块的主要原因之一,需要关注硬件健康状态。

当故障发生时,清晰的处置思路至关重要:首先通过日志精确定位问题;若有可用备份,优先采用物理恢复,速度最快;若无备份,则尝试以恢复模式启动并逻辑导出数据,尽可能减少损失。更多数据库相关的深入讨论和实践经验,欢迎在 云栈社区 交流。

参考资料

[1] MySQL数据文件坏快导致数据库无法启动, 微信公众号:mp.weixin.qq.com/s/1T-Qh0TB1jNv_63qyX0eMg

版权声明:本文由 云栈社区 整理发布,版权归原作者所有。




上一篇:构建自动化红队测试框架:大模型安全对抗性评估实践
下一篇:阿里云开源LoongSuite:为AI Agent构建可观测性数据采集基础设施
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-23 14:18 , Processed in 0.737965 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表