一、被关闭的自动统计数据收集 深入剖析MySQL 8 0的统计信息机制。其默认启用的持久化统
深入剖析MySQL 8.0的统计信息机制。其默认启用的持久化统计(innodb_stats_persistent=ON)将表的行数、索引基数等关键元数据,不仅缓存在内存(如table->stat_n_rows),更持久化存储在mysql.innodb_table_stats和mysql.innodb_index_stats系统表中。标准流程下,内存与磁盘数据保持同步:先更新内存,再写入持久化表;实例重启后,则从持久化表重新加载至内存。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
然而,一个隐蔽的风险点在于:标准的mysqldump备份与恢复流程,可能破坏这种同步,并直接损毁系统表中的统计信息。
通常,当表数据变更量超过当前统计行数的10%时,InnoDB后台线程会自动重新收集统计信息。但在特定备份恢复场景中,MySQL会通过执行/*!50606 SET GLOBAL INNODB_STATS_AUTO_RECALC=OFF */来强制禁用此自动收集功能。这导致在数据导入期间,无论插入多少行数据,统计信息都处于冻结状态。
触发此“关闭”操作的核心场景如下:
mysql系统库。--all-databases参数(这是最关键的触发条件)。因为--all-databases必然会将存放统计信息的mysql.innodb_table_stats和mysql.innodb_index_stats表一并导出。
MySQL源码中定义了is_innodb_stats_tables_included函数,专门用于检测并处理此行为。通过代码分析工具,输入提示词“定位并分析is_innodb_stats_tables_included函数的调用逻辑与作用”,可以清晰验证其机制,分析结果如下图所示。未来或可借助更智能的代码分析Agent提升此类排查效率。


问题的根源正是这一保护机制。它本意是防止导入旧的统计信息覆盖新实例,却意外导致了统计信息的完全丢失。我们来详细拆解后续影响。
统计信息丢失体现在两个层面:内存中的实时统计与磁盘上的持久化统计,两者均告失效。
首先,内存统计值失效。尽管导入的SQL文件内含innodb_table_stats的历史数据,但SQL执行时,优化器依赖的是table->stat_n_rows这一内存值。由于自动收集功能已被关闭,该内存值在数据导入全程无法得到更新。通过GDB调试器可观察到,prebuilt->table->stat_n_rows的值已归零。
其次,持久化统计被覆盖。整个导入过程可分解为:DROP TABLE -> CREATE TABLE -> INSERT。问题出在CREATE TABLE阶段——它会向innodb_table_stats表插入一条该表的新记录,从而覆盖已有的持久化统计。于是,表刚重建完,其持久化统计即被清零。随后,尽管插入了大量数据,但因自动收集关闭,系统不会重新计算。最终结果是:数据完整导入,但innodb_table_stats表中相关记录的n_rows等字段却为0,统计信息彻底缺失。
我们通过实测验证。在MySQL 8.0.23与8.0.41版本中测试,结果一致。重点关注测试库mytest的统计信息变化。
导入前,统计信息状态正常:
mysql> desc select count(*) from mytest ;
+----+-------------+--------+------------+-------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+-------+---------------+------+---------+------+-------+----------+-------------+
| 1 | SIMPLE | mytest | NULL | index | NULL | id | 5 | NULL | 65920 | 100.00 | Using index |
+----+-------------+--------+------------+-------+---------------+------+---------+------+-------+----------+-------------+
1 row inset, 1 warning (0.00 sec)
(gdb) p prebuilt->table->stat_n_rows
$3 = 65920
mysql> select * from mysql.innodb_table_stats;
+---------------+---------------+---------------------+--------+----------------------+--------------------------+
| database_name | table_name | last_update | n_rows | clustered_index_size | sum_of_other_index_sizes |
+---------------+---------------+---------------------+--------+----------------------+--------------------------+
...| stattest | mytest | 2025-06-06 15:22:09 | 65920 | 161 | 97 |...
+---------------+---------------+---------------------+--------+----------------------+--------------------------+
可见,内存统计值(65920)与持久化表记录(65920)完全同步。
导入后,问题显现:
mysql> desc select count(*) from mytest ;
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | mytest | NULL | index | NULL | id | 5 | NULL | 1 | 100.00 | Using index |
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+-------------+
1 row inset, 1 warning (0.00 sec)
(gdb) p prebuilt->table->stat_n_rows
$4 = 0
mysql> select * from mysql.innodb_table_stats;
+---------------+---------------+---------------------+--------+----------------------+--------------------------+
| database_name | table_name | last_update | n_rows | clustered_index_size | sum_of_other_index_sizes |
+---------------+---------------+---------------------+--------+----------------------+--------------------------+
...| stattest | mytest | 2025-06-06 07:00:06 | 0 | 1 | 1 | ...
执行计划估算行数锐减至1(实际应为65920),内存统计值为0,持久化表中的n_rows亦为0。统计信息已完全丢失。
此问题影响广泛,可能导致严重的性能劣化。MySQL官方已将其确认为Bug,编号:https://bugs.mysql.com/bug.php?id=98178。受影响版本涵盖5.6、5.7及8.0系列。
以下提供规避与解决方案:
A. 预防措施: 执行mysqldump备份时,避免使用--all-databases参数。改为显式指定需要备份的业务数据库,从根本上避免触发统计信息保护机制。
B. 事后检查: 数据导入完成后,立即检查mysql.innodb_table_stats与mysql.innodb_index_stats系统表。若发现大量表的n_rows字段为0或数值异常偏小,可尝试重启数据库实例。重启后,需确认innodb_stats_auto_recalc参数已恢复为ON状态。
C. 补救方案: 若已发生统计信息丢失并引发慢查询,最有效的补救措施是立即手动触发全库或受影响表的统计信息收集。执行命令:ANALYZE TABLE 表名;。可针对单表操作,或编写脚本批量处理所有受影响表,以快速恢复优化器决策准确性。
菜鸟下载发布此文仅为传递信息,不代表菜鸟下载认同其观点或证实其描述。