子查询被重复执行是性能杀手,尤其相关子查询无法物化而逐行执行;用join替代in/exists、确保索引、手动物化临时表及合理使用预编译可显著优化。

子查询被重复执行是性能杀手
嵌套子查询慢,往往不是因为逻辑复杂,而是数据库对同一子查询反复解析、计划、执行。比如 WHERE id IN (SELECT user_id FROM logs WHERE ts > NOW() - INTERVAL 1 DAY),若外层扫描 10 万行,这个子查询可能被执行 10 万次(取决于优化器是否能物化)。
- MySQL 5.6+ 默认对部分
IN子查询自动物化(临时表),但仅限于“非相关子查询”;一旦子查询里引用了外层字段(如WHERE u.id = o.user_id),就变成相关子查询,无法物化,几乎必然逐行执行 - PostgreSQL 对相关子查询默认使用 Nested Loop,除非加
LATERAL显式控制或改写为 JOIN - SQL Server 的执行计划里如果看到多次出现同一
SELECT节点(尤其带 Compute Scalar 或 Correlated Nested Loops),基本可确认重复计算
用 JOIN 替代 IN / EXISTS 是最稳的提速手段
绝大多数业务场景下,把子查询改写为显式 JOIN 后,优化器能更好利用索引、选择 Hash Join 或 Merge Join,避免逐行探查。
- 把
SELECT * FROM orders WHERE user_id IN (SELECT id FROM users WHERE status = 'active')改成SELECT o.* FROM orders o JOIN users u ON o.user_id = u.id WHERE u.status = 'active' - 注意去重:原
IN天然去重,JOIN可能因一对多产生重复行,必要时加DISTINCT或用EXISTS替代 -
EXISTS比IN更适合存在性判断,且对 NULL 安全;但若子查询返回大量数据,EXISTS仍可能走 Nested Loop —— 此时优先确保user_id和子查询过滤字段上有联合索引
预编译 SQL 本身不加速子查询,但能省掉解析开销
预编译(如 JDBC 的 PreparedStatement)只缓存语句结构和执行计划,不缓存子查询结果。它对含参数的子查询有效(如 WHERE ts > ?),但对硬编码时间范围(如 NOW() - INTERVAL 1 DAY)无加速作用 —— 因为每次执行时表达式值不同,计划可能失效。
- MySQL 中开启
query_cache_type=1(已弃用)或使用SELECT SQL_CACHE ...曾可缓存结果,但 8.0 已移除;现只能靠应用层缓存或查询结果集缓存(如 Redis) - PostgreSQL 没有查询结果缓存机制,
PREPARE仅缓存计划,且计划会随统计信息更新而失效 - 真正起效的是:固定参数 + 稳定执行计划 + 高频调用 → 这时预编译才体现出毫秒级节省
物化临时表要手动控制,别指望优化器全兜底
某些场景下,子查询结果集小、复用率高,但优化器就是不物化(比如 PostgreSQL 对 WITH 子句默认不物化,除非加 MATERIALIZED)。这时候得自己动手。
- MySQL:用
CREATE TEMPORARY TABLE tmp AS SELECT ...+ 索引,再 JOIN;注意临时表只在当前会话可见,且不能跨语句复用(除非用普通表 + 应用层管理生命周期) - PostgreSQL:明确写
WITH t AS MATERIALIZED (SELECT ...) SELECT * FROM main JOIN t ...;否则WITH只是语法糖,可能被内联展开 - SQL Server:用
SELECT ... INTO #tmp,再建索引;注意#tmp表在存储过程中有效,但不能用于函数内
物化不是银弹:临时表 IO 开销、建索引耗时、并发写冲突都得实测。尤其当子查询本身不到 10ms,而物化+JOIN 总耗时升到 20ms,就得放弃。










