SQL中的CTE是每次用到时都会重新查询还是只查询一次对查询结果进行缓存,再用到时直接用缓存的结果?
这是一个非常经典的问题。在 SQL 中,关于 CTE(Common Table Expressions,公用表表达式)是实时计算还是物化缓存,答案并不是绝对的,它取决于你使用的数据库引擎以及优化器的判断。
简单来说:在标准定义中,CTE 只是语法的简化,而不是性能的缓存。
1. 大多数数据库的默认行为:不缓存
在 MySQL(8.0+)、PostgreSQL(早期版本)和 SQL Server 中,CTE 通常被视为**内联(Inlining)**的视图。
这意味着如果你在主查询中引用了同一个 CTE 两次,优化器通常会将 CTE 的定义“拷贝”到这两个地方。结果就是:CTE 会被执行两次。
-
原因: 这样可以让优化器把主查询的
WHERE条件推送到 CTE 内部,从而利用索引提高效率。 -
代价: 如果 CTE 内部计算非常耗时(比如复杂的聚合或多表关联),重复执行会产生巨大的开销。
2. 不同数据库的具体表现
| 数据库 | 是否缓存/物化 (Materialize) | 说明 |
| PostgreSQL | 可选/自动 | 12版本之前默认物化(只查一次)。12之后默认内联,但你可以用 AS MATERIALIZED 强制它只查一次并缓存结果。 |
| SQL Server | 通常不缓存 | 优化器几乎总是将其展开(Expand)。如果需要缓存,通常建议使用临时表(#TempTable)。 |
| MySQL | 不缓存 | 主要是语法糖,优化器会将其合并到主查询中。 |
| Oracle | 自动判断 | 优化器会根据成本计算,自动决定是把 CTE 当作临时表(Materialize)还是内联(Inline)。你也可以用 /*+ MATERIALIZE */ 提示强制缓存。 |
3. 什么时候会觉得“变慢了”?
如果你发现一个复杂的 CTE 被多次引用导致查询变慢,这通常是因为数据库没有进行物化处理。
举个例子:
WITH Heavy_CTE AS (
SELECT ... FROM Big_Table -- 假设这里运行需要 10 秒
)
SELECT * FROM Heavy_CTE JOIN Heavy_CTE AS h2 ...
如果数据库不支持物化,这个查询可能会运行 20 秒,因为它把那个 10 秒的任务做了两遍。
4. 总结与建议
-
CTE 的初衷: 是为了代码的可读性和逻辑清晰,而不是为了性能优化。
-
如果你需要“只查一次”:
-
在 PostgreSQL 中,使用
WITH name AS MATERIALIZED (...)。 -
在 SQL Server 或 MySQL 中,如果性能受损,请考虑将中间结果写入 临时表(Temporary Table)。
-
底层逻辑: 数据库优化器通常很聪明,它宁愿多算一遍,也不愿轻易缓存。因为一旦缓存,它就无法针对主查询的过滤条件进行索引优化了(即“谓词下推”失效)。

浙公网安备 33010602011771号