cte在mysql中默认不物化、反复计算,性能通常更差;临时表显式物化且可建索引,性能更优;8.0.31+需显式用materialized才强制物化,但仍无法建索引;cte核心优势在于表达力与调试效率,而非性能。

CTE 在 MySQL 中没有底层性能优势,相反,它默认更慢——除非你主动干预优化器行为。
CTE 默认不物化,反复计算是常态
MySQL 8.0.31 之前,WITH 完全等价于内联子查询:每次引用 cte_name,优化器都会重新执行其定义的 SELECT。比如:
WITH sales AS (SELECT order_id, amount FROM orders WHERE status = 'paid') SELECT * FROM sales s1 JOIN sales s2 ON s1.order_id = s2.order_id + 1;
这个语句里 sales 被用了两次,MySQL(8.0.30 及更早)大概率执行两遍全表扫描或索引扫描。
- 不是“缓存”或“复用结果”,而是“重复执行”
- 数据量越大、CTE 查询越重,性能落差越明显
- 执行计划里看不到临时表节点,只有重复出现的相同子查询结构
临时表天然物化,且可建索引
CREATE TEMPORARY TABLE 是显式落地操作,结果集只算一次,后续所有 SELECT/JOIN/WHERE 都基于已存数据:
- 支持
CREATE INDEX,对后续JOIN或ORDER BY有质的提升 - 统计信息真实(行数、NULL 比例、值分布),优化器能生成稳定执行计划
- 即使中间结果超内存阈值被刷到磁盘(
tmpdir),也只发生一次
而 CTE 即使底层被优化器悄悄物化(如某些简单场景),你也无法控制、无法加索引、无法查看执行耗时拆分。
想让 CTE 真正“快”,得手动加 MATERIALIZED
MySQL 8.0.31+ 才支持 WITH cte_name AS MATERIALIZED (...) 语法,强制物化:
- 必须显式写
MATERIALIZED,不写就是默认NOT MATERIALIZED - 物化后仍不能建索引,只是避免重复计算,性能上限低于临时表
- 如果 CTE 内含非确定性函数(如
NOW()、RAND()),MATERIALIZED会失效并回退为内联 - 物化过程本身有开销:内存分配、数据拷贝、可能触发磁盘 I/O —— 小数据量反而更慢
真正有优势的不是“性能”,而是“表达力与调试效率”
CTE 的不可替代价值在开发侧:
- 递归查询只能用
WITH RECURSIVE,临时表做不到 - 同一语句中多次引用同一逻辑(如
JOIN同一个 CTE 两次),代码不冗余 - 可以逐段
SELECT *每个 CTE 验证中间结果,而临时表必须DROP后重建才能重跑 - 不污染元数据、无需权限(
CREATE TEMPORARY TABLE需要CREATE TEMPORARY TABLES权限)
这些不体现在执行时间上,但直接影响排查速度和协作成本——容易被忽略的点是:当 CTE 出现性能问题时,第一反应不该是调优 CTE,而是判断是否该换成临时表。











