子查询中order by无效且危险,应删除并移至外层;in/exists需确保字段有索引,优先用join替代;避免select*和多层嵌套,改用临时表+索引;表变量不省资源,大数据量用#temp表。

子查询里有 ORDER BY 就危险
SQL Server 不会把子查询里的 ORDER BY 传给外层,但它必须先执行完整个排序——哪怕你只想要前 10 行。一旦排序字段没索引,Sort 算子就会 spill 到 tempdb,全量物化中间结果。
常见错误写法:SELECT * FROM (SELECT id, name FROM orders ORDER BY created_time) t WHERE id > 1000。这个 ORDER BY 完全多余,还强制 SQL Server 把千万行全排一遍再过滤。
- 删掉所有子查询内的
ORDER BY,只保留在最终外层查询(且确保排序字段有索引) - 如果真需要分页,用
OFFSET/FETCH或TOP配合外层ORDER BY,别在子查询里加 - 检查执行计划:看到
Sort节点带SpillLevel="1"或警告Operator used tempdb to spill data,基本就是它
IN/EXISTS 嵌套没走索引,优化器被迫哈希物化
当子查询字段无索引、或外层没下推过滤时,SQL Server 很可能放弃嵌套循环(Nested Loops),转而用 Hash Match 处理 IN 或 EXISTS,结果就是哈希表溢出到 tempdb。
典型场景:SELECT * FROM t1 WHERE id IN (SELECT id FROM t2),但 t2.id 没索引,且 t2 有上百万行。
- 用
JOIN替代:SELECT t1.* FROM t1 INNER JOIN (SELECT DISTINCT id FROM t2) t2 ON t1.id = t2.id - 确保连接列(如
t2.id)有非聚集索引:CREATE INDEX IX_t2_id ON t2(id) - 把过滤条件尽量下推进子查询,比如
SELECT id FROM t2 WHERE status = 'active',别留到外层
多层子查询 + SELECT * 导致基数误估
优化器对嵌套子查询的行数估算非常脆弱。子查询返回 100 行,外层 JOIN 后实际产出 200 万行,但优化器仍按 100 行分配内存——结果一执行就 spill 到磁盘。
尤其危险的是 SELECT * 配合多层嵌套,比如 SELECT * FROM (SELECT * FROM (SELECT * FROM huge_table) t1) t2,没任何过滤,等于告诉 SQL Server:“请缓存全部数据”。
- 永远避免子查询中用
SELECT *,显式列出所需字段 - 拆开嵌套:把关键子查询提前写成临时表,并在连接字段上建索引:
CREATE TABLE #t2_ids (id INT PRIMARY KEY); INSERT INTO #t2_ids SELECT DISTINCT id FROM t2; CREATE INDEX IX_id ON #t2_ids(id); - 用
sys.dm_db_session_space_usage监控:SELECT session_id, internal_objects_alloc_page_count FROM sys.dm_db_session_space_usage WHERE internal_objects_alloc_page_count > 100000,快速定位肇事会话
临时表和表变量不是“轻量替代品”
很多人以为 DECLARE @t TABLE 比 #temp 更省内存,其实表变量在插入大量数据(如 >10 万行)时,同样占用 tempdb,且不维护统计信息,反而更容易触发错误执行计划。
更隐蔽的是:存储过程中循环创建临时对象却未释放,或长时间会话中反复填充大临时表,空间一直不释放。
- 大数据量场景优先用
#temp表,明确建索引;表变量仅限小数据( - 临时表用完立刻
DROP TABLE #t,别依赖会话结束自动清理 - 查谁在占空间:
SELECT OBJECT_NAME(object_id), page_count FROM tempdb.sys.dm_db_partition_stats WHERE index_id IN (0,1) ORDER BY page_count DESC
真正让 tempdb 爆掉的从来不是“嵌套语法”,而是排序、哈希、去重这些物理操作被塞进了不该塞的地方。最常被忽略的一点是:子查询里那个看似无害的 ORDER BY,往往比十层嵌套更致命。










