执行计划中出现Table Spool(SQL Server)、Materialize(PostgreSQL)或Using temporary(MySQL)是内存压力最直接信号,表明优化器被迫全量物化子查询结果,需重点核查Estimated Rows与Actual Rows偏差、内存溢出日志及子查询是否被重复执行。

看执行计划里有没有 Table Spool 或 Materialize 节点
这类节点是内存压力最直接的信号。SQL Server 出现 Table Spool (Eager)、Table Spool (Lazy),PostgreSQL 显示 Materialize,MySQL 显示 Using temporary,基本说明优化器被迫把子查询结果全量物化进内存——哪怕你只想要 10 行,它也可能先生成 50 万行中间集。
关键要看 Estimated Rows 是否远超实际业务规模:比如 users 表真实只有 2000 个 active 用户,但执行计划里 Table Spool 的预估行数标着 200000,这就是典型基数误判,后续所有内存申请都基于这个错误数字。
- 用
EXPLAIN (ANALYZE, BUFFERS)(PostgreSQL)或SET STATISTICS XML ON(SQL Server)抓真实执行路径 - MySQL 用
EXPLAIN FORMAT=TREE看是否出现MATERIALIZE关键字 - 别只信“Rows”列,重点比对
Actual Rows和Estimated Rows的倍数差
查日志里有没有 memory grant 异常或 spill 提示
内存溢出往往不报 “OOM”,而是以更隐蔽的方式失败:
- SQL Server 日志出现
Failed to allocate memory for sort,或sys.dm_os_memory_clerks中MEMORYCLERK_SQLQERESERVATIONS占比突增 → 查询执行内存被过度预占 - PostgreSQL 日志里有
could not write block XXX: No space left on device或spill to disk→ work_mem 不够,开始刷盘,这是内存即将耗尽的前兆 - MySQL 报
ERROR 2013 (HY000): Lost connection to MySQL server during query,且SHOW PROCESSLIST显示状态为Sending data卡住数分钟 → tmp_table_size 或 sort_buffer_size 已触顶
验证子查询是否被重复执行
嵌套过深最危险的不是层数本身,而是诱导优化器采用 nested-loop 策略,让内层子查询被外层每行调用一次。比如 SELECT * FROM orders WHERE user_id IN (SELECT id FROM users WHERE status = 'active'),若 orders 有 8 万行,而 users(status) 没索引,就会真实发生 8 万次全表扫描。
验证方法很简单:
- 把子查询单独拿出来跑:
SELECT COUNT(*) FROM users WHERE status = 'active',记下耗时(比如 12ms) - 再跑完整语句,用
SET STATISTICS IO ON(SQL Server)或EXPLAIN (ANALYZE)(PG)看 logical reads 是否接近「子查询单次读取 × 外层行数」 - 如果子查询逻辑读是 500,外层 8 万行,总 logical reads 达到 4000 万 → 基本坐实重复执行
临时禁用嵌套结构,用分步物化替代
当确认问题来自嵌套深度,又无法立刻重构业务逻辑时,最稳的排查手段是打断执行链——把最内层子查询提前物化成带索引的临时表,再逐步 JOIN。
不要用 SELECT * INTO #tmp FROM (...),它生成的临时表没索引,后续 JOIN 还是慢:
- 先建结构:
CREATE TABLE #tmp_active_users (user_id INT PRIMARY KEY); - 再建辅助索引:
CREATE INDEX IX_tmp_status ON #tmp_active_users (user_id); - 插入数据:
INSERT INTO #tmp_active_users SELECT id FROM users WHERE status = 'active'; - 最后改写主查询:
SELECT * FROM orders o JOIN #tmp_active_users u ON o.user_id = u.user_id;
这一步能快速验证:如果原来内存爆、连接断,现在秒出结果,就说明问题确实卡在嵌套执行模型上。注意临时表字段类型要和原表严格一致,否则隐式转换会绕过索引。

















