MySQL CTE 物化对复杂查询性能的影响
MySQL CTE 不一定物化。可以合并时,优化器可能把它并入外层查询;物化时则建立内部临时结果。性能取决于过滤范围、中间结果大小和复用方式,而不是 WITH 语法本身。建议先保留默认优化,再用相同 SQL 的 MERGE 与 NO_MERGE 对照计划,确认差异后决定是否固定策略。
下文针对 MySQL 8.4 的非递归 CTE、派生表与执行计划。官方地址:https://dev.mysql.com/doc/refman/8.4/en/。示例未实际连接数据库执行,不提供测速结论;四条演示记录也不足以代表复杂生产查询的成本。
先用一条可合并 CTE 比较两种计划
先建一个只包含必要字段的测试表。示例中的 paid 只做投影与过滤,没有聚合、去重或 LIMIT,便于观察合并和物化的差别。
-- 在独立测试库使用本示例,表中数据只用于观察计划形状。
CREATE TABLE cte_order_demo (
id BIGINT NOT NULL PRIMARY KEY,
customer_id BIGINT NOT NULL,
state VARCHAR(12) NOT NULL,
amount DECIMAL(12,2) NOT NULL,
INDEX ix_state_customer (state, customer_id)
) ENGINE=InnoDB;
INSERT INTO cte_order_demo VALUES
(1, 17, 'paid', 28.00),
(2, 17, 'paid', 62.00),
(3, 18, 'paid', 41.00),
(4, 18, 'new', 19.00);
-- 两条查询只改变提示;CTE定义及结果条件保持一致。
EXPLAIN FORMAT=TREE
WITH paid AS (
SELECT id, customer_id, amount
FROM cte_order_demo
WHERE state = 'paid'
)
SELECT /*+ MERGE(paid) */ id, amount
FROM paid
WHERE customer_id = 17;
-- 查看提示语法或解析产生的警告,不把注释存在当成提示已生效。
SHOW WARNINGS;
EXPLAIN FORMAT=TREE
WITH paid AS (
SELECT id, customer_id, amount
FROM cte_order_demo
WHERE state = 'paid'
)
SELECT /*+ NO_MERGE(paid) */ id, amount
FROM paid
WHERE customer_id = 17;
SHOW WARNINGS;
两条查询都要求返回客户 17 的已付款订单,提示写在引用 CTE 的外层 SELECT 上。MERGE 并不能推翻其他不允许合并的规则;NO_MERGE 也不是性能提升指令。读者需要看实际计划,而不是只看 SQL 中是否包含提示注释。
若计划直接呈现底表访问与过滤,应结合整体结构判断是否已合并;若出现物化及其临时结果访问,则继续关注生成了多少内容、被如何访问。不应要求所有版本、输出格式和数据规模都出现完全相同的节点文字。

物化的收益和代价取决于中间结果
| 任务约束 | 可能更有价值的方案 | 需要付出的代价 |
|---|---|---|
| 只需少量底表行 | 允许合并并联合优化过滤 | 仍取决于底表访问方式 |
| 同一结果被多处引用 | 评估物化复用 | 生成、存储与读取中间结果 |
| 中间结果很宽或很大 | 减少所选列与必要输入 | 改写必须保持结果语义 |
| 查询包含不可合并结构 | 优化物化前后的工作量 | 不能仅靠 MERGE 消除约束 |
对于已经物化的 CTE,同一条查询中的多次引用可以复用该物化结果;这不是跨请求缓存。优化器还可能为临时结果建立有助于访问的索引,但不保证每次建立,也不能像永久表一样要求一个固定的索引方案。
代价也不能只看“算一次”。生成大量行、携带大字段、构建内部访问结构以及重复读取,都属于可能的成本。优先让 CTE 只投影真正需要的字段,并把语义允许的限制放在清楚的位置;不要通过改变聚合输入或过滤时机偷偷改变结果。
物化也不等于所有行都会先无条件算完。优化器存在延迟物化等策略,派生条件下推也有其适用条件。因此,“强制物化后外层条件绝不可能影响内部工作量”同样是过度概括,应回到计划观察。
聚合与派生表会改变可选方案
GROUP BY、聚合函数、窗口函数、DISTINCT、LIMIT 等结构会限制合并。下面的 totals 按客户汇总,paid_amount 是汇总后的结果,不能把金额门槛随意挪成对每笔订单的筛选。
-- GROUP BY及聚合使这一查询块不适合被直接合并到外层。
EXPLAIN FORMAT=TREE
WITH totals AS (
SELECT customer_id, SUM(amount) AS paid_amount
FROM cte_order_demo
WHERE state = 'paid'
GROUP BY customer_id
)
SELECT customer_id, paid_amount
FROM totals
WHERE paid_amount >= 50;
-- 改为派生表仍要面对相同类型的优化约束,不是消除物化的开关。
EXPLAIN FORMAT=TREE
SELECT t.customer_id, t.paid_amount
FROM (
SELECT customer_id, SUM(amount) AS paid_amount
FROM cte_order_demo
WHERE state = 'paid'
GROUP BY customer_id
) AS t
WHERE t.paid_amount >= 50;
把 WITH 改写为 FROM 中的派生表,并不会自动消除相同的结构约束。两种写法都由优化器选择适用方案;是否保留 CTE,可以同时考虑复用、可读性和计划,而不是把某种语法当作“临时表开关”。递归 CTE 的规则不同,本文不把这个对照扩展到递归查询。

执行计划里应记录哪些证据
普通 EXPLAIN 提供计划与估计,不等于实际耗时。需要实际执行信息时,可由读者在受控环境使用下面的形式;这会真正运行 SELECT,不能当作没有负载的静态检查。
-- 此语句会真正运行SELECT;仅在允许的测试环境评估耗时与资源。
EXPLAIN ANALYZE FORMAT=TREE
WITH paid AS (
SELECT id, customer_id, amount
FROM cte_order_demo
WHERE state = 'paid'
)
SELECT /*+ NO_MERGE(paid) */ id, amount
FROM paid
WHERE customer_id = 17;
比较时至少记录:实际采用的组织方式、底表访问条件、中间结果输出行数、各访问节点的 loops,以及完整查询耗时。父子节点计时存在包含关系,不要把所有节点的时间直接相加;loops 也应与每轮行数、计时口径一起解读。估计行数和实际行数差距大时,结论应保留数据分布这一因素。
用相同参数、相同数据快照和一致的运行条件比较两个方案。结果集合必须保持一致,不能一边改变条件,一边把耗时下降归功于物化。没有原始计划与测试条件时,只能提出候选优化方向,不能报告加速比例。
保留默认策略,还是固定物化
单次引用且过滤很窄的查询,可先让优化器保持选择空间;多次引用一个较昂贵且紧凑的结果,值得评估物化复用。如果真实计划和重复测试没有稳定收益,就不必为了 SQL 看起来更“优化”而保留提示。
固定 NO_MERGE 后,应记录适用参数范围和撤销条件。数据规模、引用方式或查询条件变化,原先的取舍可能不再成立。不要为了局部查询试验而改变整个实例的合并策略,也不要在没有对照的情况下把提示复制到所有 CTE。
写 WITH 就只计算一次吗?不能仅凭语法这样判断。先看是否物化;物化结果在同一查询内的复用,也不等于后续请求还能复用。
发现 Materialize 就说明查询慢吗?不是。它只描述策略,是否合适取决于生成和使用这个结果的总工作量。
提示写了但计划没变怎么办?先读警告、核对引用名称与合并限制。相同计划不意味着提示解析成功,更不意味着某一种策略一定更快。
MySQL Instant DDL 变更列属性的边界
- 上一篇
- MySQL Instant DDL 变更列属性的边界
- 下一篇
- MySQL 复制过滤规则的风险隔离方法
-
- 数据库 · MySQL | 59分钟前 | MySQL · 数据库运维 · RESOURCE_GROUP Performance Schema MySQL RESOURCE GROUP CPU 资源 SET RESOURCE GROUP
- MySQL RESOURCE GROUP 怎样为查询分配 CPU 资源
- 369浏览 收藏
-
- 数据库 · MySQL | 1小时前 | MySQL · 主从复制 · 数据一致性 MySQL复制 replicate-wild-do-table 复制过滤 CHANGE REPLICATION FILTER
- MySQL 复制过滤规则的风险隔离方法
- 102浏览 收藏
-
- 数据库 · MySQL | 1小时前 | MySQL · InnoDB ·
- MySQL Instant DDL 变更列属性的边界
- 133浏览 收藏
-
- 数据库 · MySQL | 1小时前 |
- MySQL 重命名列时怎样避免旧应用读写失败
- 450浏览 收藏
-
- 数据库 · MySQL | 1小时前 | mysql 空间索引 SRID 地理范围 MBRIntersects
- MySQL 空间索引支持地理范围筛选的设计
- 114浏览 收藏
-
- 数据库 · MySQL | 1小时前 | MySQL · 主从复制 · 数据库运维 · 主从切换 MySQL GTID GTID自动定位 read_only super_read_only CHANGE REPLICATION SOURCE TO
- MySQL GTID 主从切换的检查清单
- 391浏览 收藏
-
- 数据库 · MySQL | 1小时前 |
- MySQL DDL 被元数据锁阻塞的定位方案
- 324浏览 收藏
-
- 数据库 · MySQL | 1小时前 |
- MySQL 直方图改善低基数条件估算的配置
- 163浏览 收藏
-
- 数据库 · MySQL | 1小时前 | 执行计划 · 查询优化 · mysql 执行计划 SHOW PROCESSLIST PROCESS权限 EXPLAIN FOR CONNECTION
- MySQL EXPLAIN FOR CONNECTION 为什么拿不到正在执行的计划
- 256浏览 收藏
-
- 数据库 · MySQL | 1小时前 |
- MySQL JSON 路径查询的生成列索引设计
- 296浏览 收藏
-
- 数据库 · MySQL | 1天前 | mysql事务 · InnoDB · mysql READ ONLY transaction_read_only 只读事务 START TRANSACTION
- MySQL 只读事务怎样减少误写风险
- 439浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 497次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 552次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 579次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 532次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 343次使用
-
- MySQL JSON_TABLE 展开数组时如何保留缺失字段
- 2026-09-10 501浏览
-
- MySQL 分区表怎么处理跨分区唯一键:分区列约束与建表取舍
- 2026-08-30 501浏览
-
- MySQL权限管理设置全攻略
- 2026-03-29 501浏览
-
- MySQL分片实现方法及常见方案解析
- 2025-06-24 501浏览
-
- MySQL表空间碎片怎么清理?超详细优化教程
- 2025-06-13 501浏览
