ZhangZhihui's Blog  

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)。

底层逻辑: 数据库优化器通常很聪明,它宁愿多算一遍,也不愿轻易缓存。因为一旦缓存,它就无法针对主查询的过滤条件进行索引优化了(即“谓词下推”失效)。

 

posted on 2026-03-10 20:59  ZhangZhihuiAAA  阅读(62)  评论(0)    收藏  举报