MySQL 导入数据后导致 SQL 性能下降
一、被关闭的自动统计数据收集
先来聊聊MySQL 8.0的统计信息机制。默认情况下,它启用了持久化统计(innodb_stats_persistent=ON)。这意味着表的行数、索引大小这些关键信息,不仅会放在内存里(比如table->stat_n_rows),还会被固化到mysql.innodb_table_stats和mysql.innodb_index_stats这两张系统表中。正常情况下,内存和表里的数据是同步的:先改内存,再落盘;重启后,再用表里的数据来初始化内存。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
但这里有个暗坑:mysqldump操作可能会打破这种同步,甚至损坏系统表中的统计信息。
通常,当表的数据修改量超过当前统计行数的十分之一时,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函数,并且分析其调用方式和作用”。结果很明确,如下图所示(在trae auto model模式下分析所得)。未来或许可以考虑用更智能的Agent来辅助代码分析,这也不失为一种高效的方法。


而问题的根源,恰恰就在这里。正是这个机制,导致了统计信息的丢失。我们接着往下分析。
二、统计信息丢失
统计信息丢失发生在两个层面:内存中的信息和持久化到表中的信息。它们双双“失灵”了。
第一,内存信息失效。虽然导入的SQL文件里包含了innodb_table_stats表的旧数据,但SQL语句执行时,优化器依赖的是table->stat_n_rows这个内存值。由于前面提到的自动收集功能被关闭了,这个内存值在数据导入过程中得不到更新。通过调试器(gdb)可以看到,prebuilt->table->stat_n_rows的值变成了0。
第二,持久化信息被覆盖。整个导入过程可以拆解为:先DROP TABLE,再CREATE TABLE,然后INSERT数据。问题出在CREATE TABLE这一步——它会覆盖innodb_table_stats表中对应表的现有记录。于是,持久化的统计信息在表刚重建完时就被清零了。紧接着,虽然插入了大量数据,但因为自动收集被关闭,系统不会去重新计算。最终结果就是:数据导完了,但innodb_table_stats表里却留下了大量值为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。统计信息彻底丢失了。
四、bug和建议
这个问题的潜在影响面其实挺广的,值得警惕。MySQL官方已将其确认为一个Bug,编号为:https://bugs.mysql.com/bug.php?id=98178。受影响的版本包括5.6、5.7和8.0系列。
那么,如何规避和解决呢?这里有几个建议:
A. 预防措施: 导出数据时,尽量避免使用--all-databases参数。只导出你真正需要的业务数据库,从根本上避免触发这个机制。
B. 事后检查: 数据导入新库后,务必检查mysql.innodb_table_stats和mysql.innodb_index_stats这两张表。如果发现大量表的统计行数(n_rows)为0或异常小,可以考虑重启一次数据库实例。重启后,确认参数innodb_stats_auto_recalc是否为ON。
C. 补救方案: 如果不幸已经中招,数据导入后出现了慢查询,最直接有效的办法就是立即手动触发全库的统计信息收集。执行命令:ANALYZE TABLE 表名; 对于受影响的表逐一处理,或者编写脚本批量处理。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
爱奇艺创始人龚宇:“跪求”AI 生成的高品质影视作品
爱奇艺龚宇:行业亟需AI影视内容,技术变革引爆创作临界点 4月15日,在第十三届中国网络视听大会上,爱奇艺创始人兼CEO龚宇发表了一项关键判断。他指出,人工智能技术正为影视行业,特别是长视频赛道,带来前所未有的振兴机遇与转型动力。 这一判断基于怎样的行业现状?龚宇剖析了传统长视频制作的深层痛点:内容
守望先锋二赛季推赛后语音,本为促交流却成辱骂温床
《守望先锋》第二赛季正式上线:新英雄登场与平衡性调整全面解析 随着《守望先锋》第二赛季的正式开启,玩家们迎来了包括新英雄在内的多项内容更新。然而,一个尚处于测试阶段的功能——赛后队伍语音交流系统,却意外成为了社区讨论的焦点。该功能允许对战双方在比赛结束后的短暂几十秒内,直接通过语音进行对话。 开发团
男子因噪音过敏住5年隔音舱:不少网友也想要同款
从噪音困扰到创业灵感:一位上海居民的隔音舱解决方案之旅 2012年,一位上海市民购置了一套临街住宅。未曾预料,随之而来的持续噪音污染,成为他此后数年间挥之不去的困扰——深夜屡屡被惊醒,长期积累甚至引发了对声音的过度敏感,导致在普通卧室内也难以获得安稳睡眠。 转机出现在他偶然接触室内隔音工程之后。凭借
小米冰淇淋3天卖2000多只:冰淇淋机子打炸了 食堂无奈求饶
小米冰淇淋3天卖2000多只:冰淇淋机子打炸了 食堂无奈求饶 4月16日最新消息,一则来自小米食堂的官方动态引发了广泛关注。就在4月15日晚间,小米食堂官方账号发布了一条充满“求生欲”的公告,核心内容是呼吁广大网友:网络上的趣味梗图大家看看开心就好,但现实中可千万别真的把我们的设备给“干冒烟”了!
日产重塑英菲尼迪:弃独立平台,推7款新车强化差异化
日产重塑英菲尼迪战略:放弃独立平台,聚焦7款新车打造核心差异化 4月16日,日产汽车首席执行官内田诚(Makoto Uchida)对英菲尼迪品牌的现状与发展路径进行了深入剖析。他指出,这一豪华品牌近年来面临的市场挑战,主要源于过往关键战略的调整,目前品牌正处在全面重塑与战略转型的关键时期。 内田诚直
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

