应使用派生表在FROM子句中预聚合,避免相关子查询重复执行;因数据库不缓存子查询结果,每行都会独立触发全量扫描与聚合,导致性能急剧下降。

直接用派生表(FROM 子句里的子查询)把聚合结果提前算好,别在多个地方重复写相同的 SUM() 或 COUNT()。
为什么重复写子查询会导致重复计算
数据库不会自动缓存子查询结果。每次你在 WHERE、SELECT 或 HAVING 里写一遍 (SELECT SUM(revenue) FROM sales WHERE region = t.region),它就真会执行一遍——哪怕 t.region 值完全一样。百万行主表,就可能触发百万次扫描+聚合。
- 相关子查询(correlated subquery)是性能杀手,尤其带聚合时
- 即使外层
GROUP BY已分组,子查询仍按每行独立执行 - 优化器极少对多层嵌套中的相同子查询做自动 unnesting,别指望它聪明
用派生表替代重复子查询的写法要点
把聚合逻辑一次性写在 FROM 子句里,起别名后 JOIN 使用。这是最可控、最易读、数据库也最容易优化的方式。
- 子查询必须加
AS alias_name,否则 MySQL 报Error Code: 1248 - JOIN 条件必须严格匹配派生表的分组键(如
region),不能漏或错位 - WHERE 过滤条件(如只算
status = 'paid')必须写在子查询内部,不能挪到外层,否则LEFT JOIN会退化成INNER JOIN - 字段类型要一致:主表
region是VARCHAR(10),派生表的region也得是,别靠CAST强转,否则索引失效
SELECT
r.region,
r.total_revenue,
COALESCE(s.paid_orders, 0) AS paid_orders,
COALESCE(s.avg_order_value, 0) AS avg_order_value
FROM regions r
LEFT JOIN (
SELECT
region,
COUNT(*) AS paid_orders,
AVG(amount) AS avg_order_value
FROM sales
WHERE status = 'paid' -- ✅ 过滤必须在这里
GROUP BY region
) AS s ON r.region = s.region;
什么时候不该用派生表?换 CTE 或物化视图
当同一个聚合结果要在多个地方复用,且逻辑复杂(比如嵌套窗口、多级过滤),派生表会让 SQL 变得冗长难维护。这时优先选 WITH CTE:
- CTE 更易读、可重用多次,且现代数据库(PostgreSQL / SQL Server / MySQL 8.0+)基本都支持
- 如果这个聚合指标高频使用(如日报统计)、数据更新不频繁,直接建汇总表或物化视图,定时刷新,比每次实时算快一个数量级
- 注意:CTE 不是视图,不自动物化;想真正避免重复计算,还得看执行计划里是否只扫一次底层表
真正容易被忽略的是类型对齐和过滤位置——哪怕语法全对,region 一边是 INT 一边是 CHAR,或者把 WHERE status = 'paid' 写在外层,都会让预聚合白做,甚至结果错误。










