子查询在存储过程中易变慢因不缓存结果、每次调用重执行;in/exists应转join以利用索引;from子句子查询需显式建临时表并索引;过滤条件应留在外层以保障复用性。

为什么子查询在存储过程中容易变慢
子查询在存储过程里不缓存结果,每次被引用就重跑一次。比如你在WHERE里写两次一样的(SELECT COUNT(*) FROM orders WHERE user_id = u.id),数据库会执行两遍——哪怕orders有500万行、还带日期范围过滤。这不是引擎偷懒,是SQL标准行为:子查询没有生命周期管理,也没有“自动物化”承诺。
IN/EXISTS子查询优先转成JOIN
当子查询出现在WHERE中用于存在性判断(如查“有订单的客户”),直接用JOIN替代最有效。因为JOIN能利用索引驱动、避免重复扫描,而IN常触发全表扫描+临时排序,EXISTS虽有短路优势,但复杂条件仍难优化。
-
IN+DISTINCT:强制去重 → 额外排序开销,逻辑读高 -
EXISTS:找到即停,但无法复用中间结果,嵌套深时执行计划易劣化 -
JOIN:一次扫描+哈希/嵌套循环,可建索引加速关联字段,后续还能加WHERE下推
示例改造:
-- 原始低效写法 SELECT c.id, c.name FROM customers c WHERE c.id IN ( SELECT o.customer_id FROM orders o WHERE o.order_date > DATEADD(month, -3, GETDATE()) ); <p>-- 改为 JOIN(SQL Server) SELECT DISTINCT c.id, c.name FROM customers c INNER JOIN orders o ON c.id = o.customer_id WHERE o.order_date > DATEADD(month, -3, GETDATE());</p>
FROM子句里的子查询必须落地为临时表
如果子查询出现在FROM中(即表子查询),且被多次引用或参与多层JOIN,别信WITH能帮你缓存。CTE在SQL Server和MySQL里基本是语法糖,执行计划里照样展开成多次子查询;PostgreSQL 12+ 要显式加MATERIALIZED才可能物化。
- SQL Server:用
#temp_table,插入后立刻建索引,例如CREATE INDEX IX_customer_id ON #cust_orders(customer_id) - MySQL:用
CREATE TEMPORARY TABLE tmp_orders AS SELECT ...,再对关键列加INDEX - PostgreSQL:用
CREATE TEMP TABLE tmp_orders AS SELECT ...,默认无WAL,速度快;需JOIN时手动CREATE INDEX - 务必显式
DROP TABLE(尤其SQL Server)——连接池复用下,残留临时表会导致后续执行失败
JOIN后WHERE条件别乱下推进子查询
有人把外层过滤提前塞进子查询里,比如把WHERE t.status = 'active'硬塞进FROM (SELECT * FROM users) t变成FROM (SELECT * FROM users WHERE status = 'active') t。这看似减少数据量,实则破坏复用性:一旦业务要查'pending',整个子查询得重写,临时表也白建了。
正确做法是保持子查询通用(只做聚合、关联、清洗),把业务维度过滤留在外层WHERE或JOIN条件里。这样同一份临时表可支撑多个下游查询,也方便加覆盖索引。
真正容易被忽略的点:临时表的索引不是“建了就行”,而是要匹配后续所有JOIN、WHERE、ORDER BY涉及的字段组合。单列索引对复合条件效果有限,别省那句CREATE INDEX IX ON #tmp(a,b,c)。










