核心问题是未消除相关性就盲目缓存;应先通过索引优化或sql改写解决内层扫描瓶颈,再评估是否需物化视图/临时表等兜底方案。

关联子查询慢,核心问题不是“能不能缓存”,而是“缓存前有没有先消除相关性”。物化视图或临时表只是兜底手段,用早了反而掩盖真正瓶颈。
为什么物化视图/临时表常被误用
很多人一看到 DEPENDENT SUBQUERY 就想建临时表,但实际执行计划里它可能根本没跑几次——外层结果集才几十行,问题其实在内层缺失索引。盲目物化会带来额外开销:
- 临时表需写磁盘(除非显式指定
ENGINE=MEMORY),建表+插入本身耗时 - 物化视图在 MySQL 中不原生支持(需手动模拟),且数据非实时,容易查到过期结果
- 如果子查询条件含
NOW()、RAND()等非确定性函数,缓存结果直接失效
先确认是否真需要缓存:看 EXPLAIN 的 type 和 rows
执行 EXPLAIN 后重点关注这两列:
-
type是ALL或index→ 内层表没走索引,优先建联合索引,比如(customer_id, status) -
rows显示子查询扫描行数 × 外层行数 ≈ 总扫描量 → 若超 10 万,说明相关性已成瓶颈,此时才考虑缓存 - 出现
MATERIALIZED标记 → 优化器已尝试自动物化,但效果差,说明结果集结构或大小不适合自动处理
临时表缓存的正确写法与陷阱
不是所有场景都适合 CREATE TEMPORARY TABLE,关键看子查询是否可预计算:
- ✅ 适合:
SELECT customer_id, AVG(amount) FROM orders GROUP BY customer_id—— 聚合结果稳定,可建索引加速后续 JOIN - ❌ 不适合:
SELECT * FROM logs WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)—— 时间窗口滑动,缓存秒级失效 - 建临时表后必须加索引:
CREATE INDEX ix_tmp_cid ON tmp_avg(customer_id),否则 JOIN 时仍是全表扫描 - 临时表名不能重复,建议带时间戳或会话 ID,避免并发冲突
比临时表更轻量的替代方案
多数情况下,改写比缓存更有效:
- 标量子查询(如
(SELECT COUNT(*) FROM logs WHERE user_id = u.id))→ 改用派生表 +LEFT JOIN,提前聚合 -
IN/NOT IN子查询 → 优先换EXISTS或LEFT JOIN ... IS NULL,避免临时表生成 - 若数据库支持 CTE(如 PostgreSQL、MySQL 8.0+),用
WITH avg_per_cust AS (...) SELECT ... JOIN avg_per_cust,语义清晰且优化器更易处理
真正卡住性能的,往往是外层一行触发一次无索引扫描。缓存只是把“慢十次”变成“慢一次再快九次”,而建对索引或改写 JOIN 能让“十次都快”。别急着物化,先看 EXPLAIN 里哪一行在扫全表。










