MySQL索引页损坏故障排查与恢复实战:一次集群Crash的完整复盘
前言
在数据库运维中,数据页损坏是最令人头疼的问题之一。它不像慢查询那样可以逐步优化,也不像连接数爆满那样可以快速扩容——页损坏往往会导致MySQL进程直接Crash,甚至引发主从切换失败、集群不可用等严重故障。
本文将完整复盘一次生产环境中因索引页损坏导致的MySQL故障,从故障现象、排查过程、根因分析到最终修复和预防措施,帮助你在遇到类似问题时能够快速定位和解决。
一、故障概述
| 项目 | 内容 |
|---|---|
| 涉及系统 | 业务系统XX集群 |
| 事件级别 | P1(严重) |
| 事件分类 | MySQL故障 |
| 影响范围 | 核心业务 |
| 是否重复 | 否 |
| 是否彻底解决 | 是 |
故障现象
MySQL主库发生Crash并自动重启,触发主从切换。主从切换完成后,新主库再次Crash重启,导致应用程序持续访问数据库失败。
经排查,MySQL重启的根本原因是某业务表的索引存在坏页(损坏的数据页)。通过重建该索引,问题得到彻底解决。
二、环境信息
2.1 故障环境配置
| 配置项 | 信息 |
|---|---|
| 环境 | 某云环境 |
| 数据库架构 | DRDS集群 |
| MySQL版本 | 5.7.x |
| 老主库IP | 10.x.x.x1 |
| 新主库IP | 10.x.x.x2 |
2.2 集群拓扑关系
┌─────────────────────────────────────────────────────────┐
│ DRDS集群 │
├─────────────────────────────────────────────────────────┤
│ │
│ 老主库: 10.x.x.x1 ──┬── 从库(基于备库创建) │
│ │ │
│ 新主库: 10.x.x.x2 ──┼── 备库(2021-12创建) │
│ │ │
│ └── 线下库(2020-12创建) │
│ │
└─────────────────────────────────────────────────────────┘
关键时间线:
- 线下库创建时间:2020-12 ✅ 正常
- 备库创建时间:2021-12 ⚠️ 可能已损坏
- 主库创建时间:2022-04 ⚠️ 基于备库创建
- 从库创建时间:2023-11 ⚠️ 基于备库创建
三、故障处理全过程
3.1 故障发现与初步定位
从MySQL错误日志中可以看到明显的数据页损坏信息:
tail -f /var/log/mysql/mysql.err.log
日志显示:
[ERROR] InnoDB: Page [page id: space=xxx, page number=xxx]
log sequence number is in the future!
[ERROR] InnoDB: Your database may be corrupt...
触发Crash的SQL(脱敏后):
SELECT MAX(id)
FROM T_XXX_XXX
WHERE PRODUCTID = 'xxxxxxxxxxxxx'
AND STATUS IN (2, 3);
3.2 主从切换分析
MySQL Crash后,系统自动触发主从切换。查看切换日志:
tail -f /var/log/cluster/agent.log
日志显示:
[INFO] Master 10.x.x.x1 is down, initiating failover...
[INFO] Promoting slave 10.x.x.61 to new master...
[INFO] Failover completed
关键问题:切换成功后,新主库10.x.x.x2同样出现Crash,说明损坏的数据页已经同步到了多个节点。
3.3 排查MySQL Bug可能性
检查是否存在Core文件:
find / -name "core.*" 2>/dev/null
ls -la /var/log/mysql/core*
结果:未发现Core文件,排除了典型的MySQL Bug导致的Crash。
3.4 故障止损方案对比
方案一:基于线下库重建主从(耗时长)
| 步骤 | 操作 | 预估耗时 |
|---|---|---|
| 1 | 将线下库切换为备库 | 5分钟 |
| 2 | 执行全量备份 | 1-2小时 |
| 3 | 基于备份创建新主从 | 30分钟 |
总耗时:约2-3小时 ❌
方案二:原地重建损坏索引(推荐✅)
| 步骤 | 操作 | 预估耗时 |
|---|---|---|
| 1 | 在从库执行CHECK TABLE定位损坏 | 2分钟 |
| 2 | DROP损坏索引 | 1分钟 |
| 3 | 重建索引 | 5分钟 |
| 4 | 主从切换 | 2分钟 |
| 5 | 对新主库执行索引重建 | 5分钟 |
总耗时:约15分钟 ✅
最终选择方案二,因为:
- 执行效率高,业务影响小
- 只涉及单表索引重建,风险可控
- 不需要全量备份恢复
3.5 具体修复步骤
Step 1: 定位损坏的表和索引
-- 在从库执行CHECK TABLE
CHECK TABLE t_xxx_xxx;
-- 查看错误日志定位具体损坏的索引
SHOW ENGINE INNODB STATUS\G
发现:损坏发生在IDX_XXX_XXX索引上
Step 2: 在从库重建索引
-- 1. 删除损坏的索引
ALTER TABLE t_xxx_xxx DROP INDEX IDX_XXX_XXX;
-- 2. 重建索引
ALTER TABLE t_xxx_xxx ADD INDEX IDX_XXX_XXX (column1, column2);
Step 3: 验证从库恢复正常
-- 执行触发Crash的SQL,验证不再报错
SELECT MAX(id)
FROM T_XXX_XXX
WHERE PRODUCTID = 'xxxxxxxxxxxxx'
AND STATUS IN (2, 3);
Step 4: 主从切换
# 执行主从切换,将从库提升为主库
# (具体命令根据集群管理工具而定)
Step 5: 对新主库执行索引重建
-- 对新主库执行同样的索引重建操作
ALTER TABLE t_xxx_xxx DROP INDEX IDX_XXX_XXX;
ALTER TABLE t_xxx_xxx ADD INDEX IDX_XXX_XXX (column1, column2);
3.6 修复验证
-- 验证主从同步状态
SHOW SLAVE STATUS\G
-- 确认 Slave_IO_Running: Yes, Slave_SQL_Running: Yes
-- 验证业务SQL正常执行
SELECT COUNT(*) FROM t_xxx_xxx WHERE PRODUCTID = 'xxxxxxxxxxxxx';
四、根本原因分析
4.1 为什么多次重启?
备库索引页损坏(2021-12)
↓
基于备库创建主库(2022-04) ←── 基于备库创建从库(2023-11)
↓ ↓
主库包含损坏索引页 从库包含损坏索引页
↓ ↓
└──────────┬───────────────────┘
↓
访问到损坏page触发Crash
↓
主从切换
↓
新主库同样存在损坏page
↓
再次Crash
结论:
- 备库在创建时就已经存在索引页损坏
- 主库和从库都是基于这个已损坏的备库创建的
- 线下库创建时间更早,数据正常
- 由于该索引是高效索引,平时查询只扫描少量索引页,一直未访问到损坏的page,直到某个特定查询触发了问题
4.2 MySQL页损坏的可能原因
| 可能原因 | 本次排查结果 |
|---|---|
| 硬件故障(磁盘坏道) | ❌ 操作系统日志无硬件错误 |
| 文件系统损坏 | ⚠️ 可能性较大 |
| 磁盘空间不足 | ❌ 监控显示空间充足 |
| MySQL软件Bug | ❌ 无Core文件,未命中已知Bug |
| 内存损坏 | ⚠️ 可能性存在 |
4.3 源码层面分析:为什么页损坏会导致Crash?
MySQL对访问到的每个索引页都会做数据校验,校验机制如下:
// btr0pcur.cc line 430
ut_a(page_is_comp(next_page) == page_is_comp(page));
代码解释:
ut_a():断言宏,条件为false时触发SIGABRT终止进程page_is_comp():检查page是否为压缩格式- 该断言验证当前page与下一个page的header是否一致
MySQL错误日志示例:
InnoDB: Assertion failure in file btr0pcur.cc line 430
InnoDB: Failing assertion: page_is_comp(next_page) == page_is_comp(page)
InnoDB: We intentionally generate a memory trap.
当索引页损坏导致相邻page的压缩格式不一致时,断言失败,MySQL主动Crash。
索引页校验流程:
开始访问索引页 → 读取page A → 读取page B
↓
校验page A和page B的header
↓
┌───────────┴───────────┐
↓ ↓
header一致 header不一致
↓ ↓
正常返回数据 断言失败(ut_a)
↓
MySQL Crash
五、预防措施与止损预案
5.1 预防措施
| 措施 | 频率 | 说明 |
|---|---|---|
| 备份恢复测试 | 每季度 | 对核心业务库的备份进行恢复测试 |
| CHECK TABLE校验 | 每季度 | 对恢复出来的表执行CHECK TABLE |
| 备份工具增强 | 持续 | 物理备份工具无法校验页损坏,需补充验证手段 |
具体操作:
-- 定期对核心表执行CHECK TABLE
CHECK TABLE t_xxx_xxx;
-- 检查所有表的完整性
SELECT
TABLE_SCHEMA,
TABLE_NAME,
CHECK_TABLE(TABLE_SCHEMA, TABLE_NAME) AS check_result
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema');
5.2 止损预案(SOP)
当遇到MySQL页损坏故障时,按以下优先级执行:
优先级1:原地重建损坏索引(最快)
-- 1. 定位损坏的表和索引
CHECK TABLE table_name;
-- 2. 删除损坏索引
ALTER TABLE table_name DROP INDEX index_name;
-- 3. 重建索引
ALTER TABLE table_name ADD INDEX index_name (column1, column2);
优先级2:基于正常节点重建
# 1. 找到数据正常的节点(如下线库)
# 2. 从正常节点导出数据
mysqldump --single-transaction --master-data=2 db_name table_name > table.sql
# 3. 导入到问题节点
mysql db_name < table.sql
优先级3:全量备份恢复(最后手段)
# 1. 从最近的正常备份恢复
xtrabackup --prepare --target-dir=/backup/full
xtrabackup --copy-back --target-dir=/backup/full
# 2. 重建主从关系
CHANGE MASTER TO ...
START SLAVE;
5.3 监控改进
| 监控项 | 当前状态 | 改进方案 |
|---|---|---|
| MySQL Crash告警 | ✅ 已有 | 保持 |
| 主从切换告警 | ✅ 已有 | 保持 |
| CHECK TABLE定期执行 | ❌ 缺失 | 新增:每月执行 |
| 备份恢复测试 | ❌ 缺失 | 新增:每季度执行 |
新增监控脚本:
#!/bin/bash
# check_table_integrity.sh - 定期检查表完整性
DB_HOST="localhost"
DB_USER="monitor"
DB_PASS="xxxxx"
LOG_FILE="/var/log/table_check.log"
echo "=== Table Integrity Check at $(date) ===" >> $LOG_FILE
mysql -h $DB_HOST -u $DB_USER -p$DB_PASS -e "
SELECT
TABLE_SCHEMA,
TABLE_NAME,
CHECK_TABLE(TABLE_SCHEMA, TABLE_NAME) AS status
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA IN ('your_core_db')
" >> $LOG_FILE
# 检查是否有错误
if grep -q "error\|corrupt" $LOG_FILE; then
# 发送告警
curl -X POST "https://your-alert-webhook" -d "MySQL表完整性检查发现异常"
fi
六、经验总结
6.1 本次故障的关键教训
- 备份不等于安全:物理备份工具不校验数据页完整性,需要额外补充验证手段
- 历史数据会传染:基于损坏数据创建的节点会继承损坏,需要在创建新节点前做完整性校验
- 高效索引隐藏问题:损坏的索引页如果未被访问,问题可能潜伏数月甚至数年
- 主从切换不是万能药:如果损坏数据已同步,切换后问题依然存在
6.2 改进措施清单
| 序号 | 改进措施 | 责任方 | 状态 |
|---|---|---|---|
| 1 | 核心业务库增加季度备份恢复测试 | DBA团队 | 已纳入SOP |
| 2 | 新增CHECK TABLE定期执行监控 | 监控团队 | 已完成 |
| 3 | 编写《MySQL页损坏修复SOP》文档 | DBA团队 | 已完成 |
| 4 | 新节点创建前执行表完整性校验 | DBA团队 | 已纳入流程 |
| 5 | 评估开启页校验相关参数 | DBA团队 | 待评估 |
七、附录
7.1 相关命令速查
-- 检查表完整性
CHECK TABLE table_name;
-- 查看InnoDB状态
SHOW ENGINE INNODB STATUS\G
-- 查看主从状态
SHOW SLAVE STATUS\G
# 查看MySQL错误日志
tail -f /var/log/mysql/error.log
本文为原创内容,欢迎转载,请注明出处。

浙公网安备 33010602011771号