记一次 MySQL 数据库恢复引发的慢查询问题
案发经过
今天在公司遇到个诡异的问题。另一位实习生同事对测试环境的 MySQL 数据库进行了恢复操作。原本以为只是常规操作,结果恢复完成后,昨天还能在 2 秒内跑完的查询,今天跑了 10 分钟都没出结果。
此时测试服务器的情况非常糟糕:CPU 占用率直接飙升到 100%,内存占用也居高不下。
排查过程
1. 初步检查
找技术经理用 Root 账号登录数据库查看状态,发现那几个查询语句一直在执行中,且耗时都已经超过了 10 分钟。 尝试 Kill 掉这些查询进程后,服务器的 CPU 和内存瞬间恢复正常。但只要业务端再次触发那个 SQL 查询,服务器立马又“瘫痪”了。
2. 尝试重启
起初怀疑是 SQL 导致了死锁,或者有什么隐藏进程阻塞了数据库。于是简单粗暴地重启了几次数据库服务,但问题依旧,没有任何改善。
3. 分析执行计划
既然重启无效,只能深入分析了。
查看慢查询日志,确认就是那几个业务查询在拖后腿。
接着查看这些 SQL 的执行计划(EXPLAIN),惊讶地发现:很多查询竟然都不走索引了! 全表扫描导致了查询时间无限拉长。
这就很离谱了。这些 SQL 在昨天(恢复数据前)还能正常运行,且正式环境也是完全相同的 SQL 和索引结构,一直运行良好。这说明问题不在 SQL 写法或索引缺失上。
解决
技术经理建议尝试优化 SQL 或添加索引,但我认为既然正式环境没问题,且昨天也没问题,说明结构本身是合理的。 结合“刚做过数据库恢复”这个操作,我推测可能是数据库的统计信息(Statistics)在恢复过程中丢失或失效了,导致 MySQL 优化器误判,选择了全表扫描而不是走索引。
于是,我尝试对相关表执行了分析命令:
ANALYZE TABLE 表名;结果立竿见影! 执行完分析后,再次查看执行计划,查询终于重新走了索引。业务查询速度也恢复到了秒级,服务器负载瞬间降了下来。
💡 深度解析:关于 ANALYZE TABLE
1. 它到底干了什么?
ANALYZE TABLE 的主要作用是重新统计索引的基数(Cardinality)。
MySQL 的优化器(Optimizer)在决定是否使用索引时,依据的是成本模型(Cost Model)。而成本计算的核心输入就是索引区分度。
- MySQL 会随机采样数据页(InnoDB 默认采样 8 个页,可配置)。
- 估算每个索引中有多少个不同的值(基数)。
- 如果采样结果显示某个索引的基数太小(重复值太多),优化器就会认为“反正大部分数据都要读,不如直接全表扫描快”,从而放弃索引。
在数据恢复或大量导入后,数据分布发生了剧烈变化,但统计信息可能还是旧的(或者为空),导致优化器拿着错误的情报做出了错误的决策。
2. 各个 MySQL 版本的表现
- MySQL 5.5 及以前:统计信息是非持久化的。重启数据库后,统计信息会丢失,MySQL 会在第一次访问表时重新计算。这通常不会导致一直不走索引,但可能会导致数据库刚启动时产生瞬间的性能抖动。
- MySQL 5.6 及以后(主流):引入了持久化统计信息(Persistent Optimizer Statistics),默认开启(
innodb_stats_persistent=ON)。统计信息存储在磁盘的mysql.innodb_table_stats表中。- 优点:重启后统计信息不丢失,执行计划稳定。
- 缺点:这也意味着,如果你通过非标准方式(如直接拷贝文件、某些恢复工具)恢复数据,或者大量数据变动后自动更新机制(
innodb_stats_auto_recalc)没有及时触发,统计信息就会长期处于“过时”状态,必须手动ANALYZE才能纠正。
3. 这种问题出现的概率大吗?
非常大,尤其是逻辑备份恢复后。
- 逻辑恢复(如 source .sql 文件):本质是执行成千上万条
INSERT语句。虽然 InnoDB 默认在表数据变更超过 10% 时会自动重新计算统计信息,但在高负载恢复过程中,这个异步动作可能会滞后,或者因为锁竞争而失败。 - 物理恢复(如 XtraBackup):通常会连同统计信息表文件一起恢复,出现概率相对较小,但也存在统计信息损坏的可能。
最佳实践:在生产环境中,每当进行全量数据迁移、大批量数据导入/删除操作后,建议把 ANALYZE TABLE 作为标准收尾动作执行一遍,以确保“情报”准确。
📝 总结
数据库恢复或大量数据导入导出后,可能会导致索引统计信息不准确。MySQL 优化器依赖这些统计信息来决定执行计划。当统计信息偏差过大时,优化器可能会放弃索引而选择全表扫描。
下次遇到这种“昨天好好的,今天突然不走索引”的情况,且 SQL 没变动,不妨先 ANALYZE TABLE 一下,强制更新统计信息。
发现错误或想要改进这篇文章?
在 GitHub 上编辑此页