嵌套查询本身不直接触发磁盘io波动,真正原因是子查询中order by/group by或结果过大导致临时表/排序落盘;通过sort_merge_passes和created_tmp_disk_tables状态变量及explain format=json中materialized_from_subquery的using_filesort/using_temporary_table可精准定位;改写join后仍抖动常因未控中间结果大小、类型不匹配致索引失效或驱动表无索引;临时缓解需同步调大tmp_table_size与max_heap_table_size至相同值,并谨慎增大sort_buffer_size;根因常是orm/bi隐式添加的无意义排序,应从应用层去除或加limit截断。

嵌套查询本身不直接触发磁盘 IO 波动,真正导致 IO 抖动的是子查询执行过程中被迫落盘的临时表或排序操作——尤其是当子查询含 ORDER BY、GROUP BY 或结果集过大,而 tmp_table_size 与 sort_buffer_size 同时不足时。
怎么确认是子查询里的排序在刷磁盘
别只看慢日志或“卡顿”现象。先抓准两个关键状态变量:
-
SHOW STATUS LIKE 'Sort_merge_passes';持续上升 → 子查询内部有排序且已溢出磁盘 -
SHOW STATUS LIKE 'Created_tmp_disk_tables';在执行该语句前后跳变 → 子查询生成了磁盘临时表
再配合 EXPLAIN FORMAT=JSON 看子查询块(query_block 下的 materialized_from_subquery)是否带 "using_filesort": true 或 "using_temporary_table": true。如果都有,基本锁死是子查询排序+落盘双杀。
为什么改写成 JOIN 后 IO 还抖?
不是 JOIN 不行,而是改写没控住中间结果大小。常见翻车点:
- 子查询没加
ORDER BY ... LIMIT N,JOIN 前就物化了 50 万行 →tmp_table_size压不住,直接写磁盘 - JOIN 条件字段类型不一致,比如
orders.user_id是BIGINT,但子查询里用了字符串'123'→ 索引失效,全表扫描+回表,IO 队列瞬间拉满 - 没限制子查询驱动表的索引范围,例如
SELECT ... FROM t1 WHERE id IN (SELECT id FROM t2 WHERE status = ?),但t2(status)没索引 → 每次外层匹配都扫一遍t2,IO 请求爆炸式增长
临时缓解:会话级参数要同步调,不能只调一个
tmp_table_size 和 max_heap_table_size 必须设成相同值,MySQL 取二者较小者生效。只调大其中一个等于没调:
- 查当前值:
SELECT @@tmp_table_size, @@max_heap_table_size; - 会话级紧急修复:
SET SESSION tmp_table_size = 268435456; SET SESSION max_heap_table_size = 268435456;(即 256MB) - 注意:这个值对每个连接独占,100 并发就吃掉 25.6GB 内存,别全局硬设
同时可临时加大 sort_buffer_size(如设为 4194304),但仅限该报表 SQL 所在会话 —— 它和前两个参数不共享内存池,但会叠加内存压力。
最容易被忽略的根因:子查询里藏着隐式排序
很多 ORM 或 BI 工具生成的子查询会自动补 ORDER BY(比如分页导出逻辑),你肉眼看不到,但 EXPLAIN FORMAT=JSON 里能看到 "ordering_operation": {"using_filesort": true}。这种排序不是业务需要的,纯粹是工具行为,却把整个子查询拖进磁盘。解决方式不是调参,是让应用层去掉无意义排序,或用 LIMIT 强制截断结果集规模 —— 即使只加 LIMIT 10000,也能拦住 90% 的磁盘临时表生成。










