cte不一定缓存结果,需看执行计划:postgresql 12+默认内联展开、重算多次,仅加materialized才物化;hologres、sql server等才默认复用。

CTE 真的会缓存结果吗?先看执行计划
不是所有数据库都默认复用 CTE 结果。PostgreSQL 12+ 默认将简单 CTE INLINE(即展开为子查询,每次引用都重算),而 Hologres、SQL Server 等才支持真正意义上的物化复用。关键得看执行计划里有没有 CTE Scan 节点——如果有,说明它被当成临时表扫描了一次;如果只有多次 Subquery Scan 或嵌套 Seq Scan,那大概率没复用。
验证方法很简单:
- 在查询前加
EXPLAIN (ANALYZE, BUFFERS) - 找 CTE 名称是否只出现一次扫描动作
- 对比启用
hg_cte_strategy = 'REUSE'(Hologres)或WITH RECURSIVE(PostgreSQL 递归场景)前后的节点数变化
什么时候必须显式控制 CTE 复用策略
当 CTE 涉及大表 JOIN、窗口函数或复杂聚合,且被主查询引用 ≥2 次时,不控制策略极易触发重复计算。比如下面这个典型场景:
WITH base AS ( SELECT u.id, u.name, COUNT(o.id) AS order_cnt FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id, u.name ) SELECT * FROM base WHERE order_cnt > 0 UNION ALL SELECT * FROM base WHERE order_cnt = 0;
在 PostgreSQL 中,默认行为是把 base 展开两次,LEFT JOIN ... GROUP BY 执行两遍。此时应:
- Hologres:设
SET hg_cte_strategy = 'REUSE'; - PostgreSQL:改用
MATERIALIZED关键字(v12+ 支持):WITH base AS MATERIALIZED ( ... ) - SQL Server:无需额外操作,CTE 默认按需物化
CTE 和子查询性能差异在哪?别只看写法
表面看 CTE 更清晰,但性能可能更差——尤其当优化器选择 inline 时,它比内联视图(FROM (SELECT ...) AS t)还多一层解析开销。真正影响性能的是三点:
- 是否真的避免了重复计算(查执行计划确认)
- CTE 输出列是否过多(宽表物化会吃更多内存和临时空间)
- 后续引用是否带过滤条件(如
WHERE下推失败,导致全量物化后再过滤)
例如,若 base CTE 返回 100 列,但主查询只用其中 3 列 + WHERE status = 'active',最好把过滤提前写进 CTE 内部,而不是依赖外部裁剪。
递归 CTE 的重复问题更隐蔽
递归 CTE(WITH RECURSIVE)天然不适用“复用”逻辑,它的锚点和递归成员是分轮次执行的。但容易忽略的是:如果递归成员里又嵌套了一个非递归 CTE,那个嵌套 CTE 可能被每轮都重新计算。
比如构建组织树时,在递归部分反复调用一个计算部门预算的 CTE,就会导致预算 SQL 执行 N 次(N = 树深度)。解决方式只有两个:
- 把预算计算提到最外层 CTE,作为预加载数据集传入递归体
- 改用临时表(
CREATE TEMP TABLE)预先算好并建索引
CTE 的“临时性”是双刃剑:它不落盘、不锁表,但也意味着没有统计信息、无法索引、复用与否完全依赖优化器判断——这点在跨版本迁移或换数据库时最容易翻车。










