在 SQL 里,CTE(Common Table Expression,公用表表达式)是通过 WITH 子句定义的临时命名结果集。关于它是否会被物化,答案并不是简单的“会”或“不会”,而是取决于数据库优化器的决策以及具体写法。

CTE 的本质
从语义上讲,CTE 更像是一个可被多次引用的查询片段。标准 SQL 并没有规定 CTE 必须被物化,因此多数关系型数据库默认会将 CTE 内联到主查询中,就像把它写成子查询一样。
内联展开的例子
下面这段 PostgreSQL 风格的 SQL 中,cte_demo 很可能被优化器直接展开:
WITH cte_demo AS ( SELECT id, name FROM users WHERE status = 1 ) SELECT a.id, b.name FROM orders a JOIN cte_demo b ON a.user_id = b.id;
什么情况下会被物化
当同一个 CTE 在主查询中被多次引用,或者优化器认为重复计算代价过高时,数据库可能选择将其结果写入内存或临时表,这就是物化。例如在 PostgreSQL 中使用 MATERIALIZED hint 可显式控制:
WITH cte_mat AS MATERIALIZED ( SELECT id FROM big_table WHERE create_time > '2023-01-01' ) SELECT * FROM cte_mat a JOIN cte_mat b ON a.id = b.id;
不同数据库的差异
| 数据库 | 默认行为 |
|---|---|
| MySQL | 8.0 前派生表合并,CTE 通常内联;复杂时可能物化 |
| PostgreSQL | 单次引用内联,多次引用可能自动物化 |
| SQL Server | 通常内联,由优化器决定是否spool |
如何从执行计划确认
判断是否物化最可靠的方式是看执行计划。若计划中出现“Temporary table”“Materialize”“CTE Scan”等节点,说明发生了物化。以 PostgreSQL 为例:
EXPLAIN WITH c AS MATERIALIZED (SELECT id FROM t1) SELECT * FROM c, c c2 WHERE c.id = c2.id; -- 计划中可见 Materialize 与 CTE Scan 节点
编写建议
- 不要默认认为 CTE 会带来额外开销
- 多次引用大结果集时,可考虑显式物化或改为临时表
- 通过 EXPLAIN 观察真实执行方式
CTE 是逻辑结构,物化是物理决策。理解优化器视角,才能写出既清晰又高效的 SQL。