子查询本身不占临时空间,真正导致临时表溢出的是排序、哈希、去重、物化等操作;判断依据是执行计划中是否出现Using temporary(MySQL)、SpillLevel="1"(SQL Server)或Plan Rows远超LIMIT预估行数(PostgreSQL),而非单纯存在子查询。

子查询本身不占临时空间,真正让 TempDB、tmp_table_size 或磁盘临时表爆掉的,是排序、哈希、去重、物化这些操作——改写目标不是“去掉子查询”,而是切断中间结果膨胀链。
怎么看是不是子查询惹的祸?
别靠猜,直接看执行计划里的物化和溢出信号:
- MySQL:运行
EXPLAIN FORMAT=tree,如果Extra列在子查询行出现Using temporary,说明物化发生在内层,且大概率已落盘 - SQL Server:开
SET STATISTICS XML ON,找Sort或Hash Match节点是否有SpillLevel="1",或警告Operator used tempdb to spill data - PostgreSQL:用
EXPLAIN (ANALYZE, BUFFERS),重点看Plan Rows—— 如果外层LIMIT 10却显示百万级预估行数,说明排序/分组没下推,全量物化已发生
删掉子查询里的 ORDER BY 和 DISTINCT
它们对外层无效,只增加内存申请和 spill 风险:
-
SELECT * FROM t1 WHERE id IN (SELECT id FROM t2 ORDER BY created_at DESC)→ORDER BY白占内存,删掉 -
SELECT DISTINCT x FROM t1 WHERE x IN (SELECT DISTINCT x FROM t2)→ 外层无法复用内层排序流,被迫建 hash table,改成JOIN (SELECT DISTINCT x FROM t2) t2 ON t1.x = t2.x并确保x有索引 - Oracle 中视图含
GROUP BY+ 外层LIMIT,排序一定发生在截断前;PostgreSQL 同理,视图里ORDER BY几乎从不被下推
JOIN 替代 IN/EXISTS 时必须加约束
盲目改写可能更慢甚至逻辑错误:
- 先用
EXPLAIN FORMAT=JSON确认原语句是否真触发了materialized_from_subquery;如果不是(比如走 semi-join),改写反而破坏优化路径 - 子查询字段必须有索引,否则
JOIN会变成嵌套循环扫描,比物化还慢 -
NOT IN必须排除 NULL:子查询若含NULL,整个条件恒为FALSE;应改用NOT EXISTS - JOIN 后务必加
DISTINCT或GROUP BY,否则关联放大导致重复行,额外去重开销更大
临时参数调高 ≠ 问题解决
tmp_table_size 和 max_heap_table_size 必须设成相同值,否则以小者为准;但即使设对了,仍可能落盘:
- 字段含
TEXT、BLOB或超宽VARCHAR(2000)→ MySQL 按最大长度预估内存,直接跳过内存表 - 视图定义含
SELECT *或JSON_EXTRACT(col, '$.name')参与ORDER BY→ 无法压缩结构,强制落盘 - 并发连接多时,每个连接独立分配内存;设成 1G × 100 连接 = 100G,系统开始 swap,
innodb_buffer_pool_size被挤占,整体更慢 - 真正该盯的是
SHOW GLOBAL STATUS LIKE 'Created_tmp%'和执行计划,而不是无脑调参
临时空间暴涨的本质是数据库被迫把不该物化的中间结果全量加载进内存或磁盘——关键不在语法嵌套,而在执行路径是否可控。最常被忽略的一点:哪怕你把所有子查询都改成了 JOIN,只要 ORDER BY 字段没索引,照样触发 External sort 写磁盘临时文件。

















