子查询本身不直接产生磁盘临时文件,真正触发落盘的是隐式临时表无索引+排序未走索引+内存缓冲不足三者叠加;需同步调大tmp_table_size和max_heap_table_size,并优先优化SQL与索引。

子查询本身不直接产生磁盘临时文件,真正触发落盘的是它引发的隐式临时表 + 排序操作没走索引 + 内存缓冲区不足三者叠加。
子查询为什么总带出Using temporary和大量#sql_*文件
MySQL执行子查询时,会为内层结果建内存临时表(tmp_table_size / max_heap_table_size 控制上限),但这个表默认无索引。一旦外层要GROUP BY或ORDER BY,就得对临时表再排序——而临时表没索引,必然触发Using filesort;若排序数据量超sort_buffer_size,就落盘生成#sql_*临时文件。
- 常见错误写法:
SELECT * FROM orders WHERE user_id IN (SELECT user_id FROM users WHERE status = 'active')—— 子查询返回全量user_id,外层JOIN或聚合时无索引可依 - 更隐蔽的坑:
SELECT (SELECT COUNT(*) FROM logs l WHERE l.order_id = o.id) cnt FROM orders o—— 每行都触发一次子查询,每次都在内存建临时表,累积占用高 - 注意:子查询返回单值(标量)一般不建临时表;返回多行(行子查询/表子查询)才建
怎么判断是不是sort_buffer_size不够导致落盘
别一看到#sql_*就调大sort_buffer_size——它只管排序阶段,不管临时表本身。先确认是不是真卡在排序:
- 用
EXPLAIN FORMAT=JSON查执行计划,看"using_filesort": true且"rows_examined_per_scan"远大于实际输出行数 - 运行
SHOW STATUS LIKE 'Sort_merge_passes';,值持续上升说明频繁外部归并排序 - 对比
Sort_scan和Sort_range:前者高说明是全表扫描后排序,后者高说明范围扫描后仍要排,大概率缺索引 - 执行
SET SESSION sort_buffer_size = 4194304; SELECT ... ORDER BY ... LIMIT 100;,如果小结果集不落盘,但全量跑就爆,说明是数据规模问题,不是buffer小
真正该调的两个内存参数:tmp_table_size和max_heap_table_size
sort_buffer_size调得再大,也救不了临时表本身超限——决定“内存临时表能否撑住”的是tmp_table_size和max_heap_table_size中较小的那个值。它们必须同步设,否则改一个等于白改。
- 查当前值:
SHOW VARIABLES LIKE 'tmp_table_size';和SHOW VARIABLES LIKE 'max_heap_table_size'; - 会话级临时生效:
SET SESSION tmp_table_size = 67108864; SET SESSION max_heap_table_size = 67108864;(64MB) - 生产环境建议 64M–256M,但必须监控
Created_tmp_disk_tables / Created_tmp_tables比值,长期高于5%就得优化SQL或加索引 - 注意:该设置对已建立连接无效,新连接才生效;已创建的临时表也不会自动迁移回内存
比调参数更有效的三件事
参数只是兜底,根治靠SQL重写和索引设计:
- 把子查询改成
JOIN,例如WHERE id IN (SELECT ...)→INNER JOIN (SELECT DISTINCT ...) t ON t.id = o.id,让优化器有机会物化中间结果并建索引 - 为子查询结果字段建联合索引,比如子查询常查
status, created_at,就建INDEX(status, created_at),减少内层扫描行数 - 用
EXISTS替代IN,尤其当子查询可能返回NULL时,IN会因三值逻辑额外膨胀中间集 - 禁用
SELECT *在子查询里——只取必要字段,能显著缩小临时表体积,有时直接从落盘变内存
临时文件是否落盘,最终取决于“临时表大小”和“排序数据量”这两个数字,而不是你写了几个子查询。索引覆盖不到的地方,调再大的buffer也只是延缓崩溃时间。

















