当前位置:首页 > 文章列表 > 数据库 > MySQL > CTE 物化代价怎么配置或排查

CTE 物化代价怎么配置或排查

来源:17golang原创 2026-09-13 06:29:48 0浏览 收藏

MySQL 的 CTE(公共表表达式)变慢,不等于“物化一定有问题”。优化器会在把 CTE 合并进外层查询块和写入内部临时表之间选择;合并有利于条件下推,物化则可能让同一个 CTE 在一次查询中复用,还可能为引用自动添加索引。真正要排查的是:当前语句走了哪条路径,物化生成了多少行,以及下游连接是否值得这次准备成本。

官方文档:https://dev.mysql.com/doc/refman/8.4/en/

要点速览
  • 先看执行计划,再决定是否干预;不要一上来全局关闭 derived_merge。
  • NO_MERGEMERGE 适合单条 SQL 的 A/B 对照,会话级 optimizer_switch 只适合实验。
  • 多次引用、递归 CTE、聚合或窗口函数会改变物化成本,最终要结合实际行数和耗时判断。

先确认 CTE 到底有没有物化

先用同一份只读查询做估算和实测。EXPLAIN 只描述计划,不会因为查看计划就把 CTE 真正跑完;EXPLAIN ANALYZE 会执行语句,更适合放在测试库或只读副本上观察真实行数和耗时。

-- 先看优化器估算的树形计划
EXPLAIN FORMAT=TREE
WITH recent_orders AS (
    SELECT customer_id, order_id, total_amount
    FROM orders
    WHERE created_at >= '2026-01-01'
)
SELECT c.customer_id, r.total_amount
FROM customers AS c
JOIN recent_orders AS r ON r.customer_id = c.customer_id;

-- 只在可接受执行成本的环境做实测
EXPLAIN ANALYZE
WITH recent_orders AS (
    SELECT customer_id, order_id, total_amount
    FROM orders
    WHERE created_at >= '2026-01-01'
)
SELECT c.customer_id, r.total_amount
FROM customers AS c
JOIN recent_orders AS r ON r.customer_id = c.customer_id;

树形计划里若出现物化相关节点,说明 CTE 没有简单地并入外层;但不要只看节点名字。比较 estimated rows、actual rows、loops 和执行时间:估算远小于实际行数,往往比“是否物化”更值得先修正统计信息或过滤条件。

MySQL CTE 查询块在合并与内部临时表物化之间选择的结构示意图
图1:MySQL CTE 在外层查询块中的合并路径与内部临时表物化路径示意,不代表真实执行截图。

需要固定策略时,先用语句级提示

如果同一 CTE 被多次引用,物化一次并复用可能更合适;如果外层过滤条件很强,合并后让条件进入底层表,反而可能少读很多数据。可以先用提示做对照,而不是直接改服务器级设置。

-- 只对本条语句尝试物化,便于和默认计划做对照
WITH recent_orders AS (
    SELECT customer_id, order_id, total_amount
    FROM orders
    WHERE created_at >= '2026-01-01'
)
SELECT /*+ NO_MERGE(recent_orders) */
       c.customer_id, r.total_amount
FROM customers AS c
JOIN recent_orders AS r ON r.customer_id = c.customer_id
WHERE c.customer_id = 1001;

-- 反向测试合并,让外层过滤尽量参与底层访问
WITH recent_orders AS (
    SELECT customer_id, order_id, total_amount
    FROM orders
    WHERE created_at >= '2026-01-01'
)
SELECT /*+ MERGE(recent_orders) */
       c.customer_id, r.total_amount
FROM customers AS c
JOIN recent_orders AS r ON r.customer_id = c.customer_id
WHERE c.customer_id = 1001;

提示只改变当前语句的尝试方向,并不保证所有规则都能合并:聚合、窗口函数、DISTINCTGROUP BYLIMIT、集合操作等构造本身可能阻止合并。物化也不是无索引的黑盒,MySQL 可能按引用方式为物化结果增加相关索引。

多次引用时要特别留意两件事:CTE 物化通常在一次查询中只生成一次,但不同引用可能需要不同的访问方式;递归 CTE 则始终物化。这里的成本不能只用“临时表变慢”一句话概括。

MySQL 物化 CTE 作为内部临时表并被多个引用使用自动索引的结构示意图
图2:物化 CTE、内部临时表、多个引用与可能生成的访问索引之间的关系示意。

全局开关只做会话级实验

optimizer_switch 里的 derived_merge 默认开启,用于控制优化器是否尝试把派生表、视图和 CTE 合并到外层查询块。排查时可以在当前连接临时关闭它,观察计划变化;不要把一次 SQL 的异常直接改成全局配置。

-- 仅在当前连接关闭 CTE/派生表合并,保留其他开关原值
SET SESSION optimizer_switch = 'derived_merge=off';

-- 在同一连接恢复默认的合并尝试
SET SESSION optimizer_switch = 'derived_merge=on';

-- 记录当前开关,避免凭记忆判断环境差异
SELECT @@SESSION.optimizer_switch;

如果关闭合并后查询明显变慢,说明这条语句可能依赖条件下推或底层索引访问;如果关闭后反而稳定,继续比较物化结果规模和连接方式。不要把 materialization 开关与 CTE 的 derived_merge 混为一谈:前者主要涉及子查询物化策略,当前问题先围绕 CTE 的合并决策定位。

用 optimizer_trace 找到“为什么这么选”

当两个计划看起来差不多,或只看到物化节点却不知道代价来源,可以在当前会话打开优化器跟踪。它只记录本会话执行的语句,适合把合并尝试、延迟物化和访问路径判断留成证据。

-- 打开当前会话的优化器跟踪
SET optimizer_trace = 'enabled=ON';

-- 执行待排查的只读 CTE 查询
WITH recent_orders AS (
    SELECT customer_id, order_id, total_amount
    FROM orders
    WHERE created_at >= '2026-01-01'
)
SELECT customer_id, SUM(total_amount) AS amount
FROM recent_orders
GROUP BY customer_id;

-- 查看本会话最近一次跟踪结果
SELECT TRACE
FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;

-- 完成排查后关闭跟踪
SET optimizer_trace = 'enabled=OFF';

最后把三份材料放在一起看:执行计划说明“走了什么”,实际分析说明“花了多少”,optimizer trace 说明“为什么这样选”。若实际行数长期偏离估算,先检查过滤条件、索引和统计信息;若物化结果很大且只被引用一次,优先测试合并;若结果会被多次复用,再评估一次物化和自动索引是否值得。

现象先看什么处理方向
CTE 结果很大,只引用一次actual rows、过滤是否能下推对照 MERGE,减少 CTE 输出列和行
CTE 被多处引用每个引用的连接条件与访问路径对照 NO_MERGE,观察复用和自动索引收益
计划估算与实测相差很大统计信息、数据分布、条件选择性先修正估算,再决定是否强制物化

常见问题

关闭 derived_merge 就等于强制所有 CTE 物化吗?

它只表示当前会话不再尝试合并,仍要受 CTE 结构和其他优化规则影响。更精确的单条语句控制优先使用 NO_MERGE

物化结果一定会落盘吗?

不能仅凭 CTE 语法判断内存或磁盘位置。应结合执行计划、实际耗时和临时表相关指标分析,不要把“内部临时表”直接等同于磁盘文件。

为什么 EXPLAIN 看起来很快,真实查询却很慢?

EXPLAIN主要给出估算计划,不执行完整查询。用安全环境运行 EXPLAIN ANALYZE,再比较 actual rows、loops 和耗时,才能判断物化代价是否真的成为瓶颈。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go filepath.WalkDir 返回 DirEntry 时怎么读取文件类型Go filepath.WalkDir 返回 DirEntry 时怎么读取文件类型
上一篇
Go filepath.WalkDir 返回 DirEntry 时怎么读取文件类型
Go selectdefault 怎么处理取消信号
下一篇
Go selectdefault 怎么处理取消信号
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    110次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    28次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    44次使用
  • AGI-Eval大模型评测平台:权威榜单、数据集与人机协同评测方案
    AGI-Eval
    AGI-Eval是由上海交大等高校联合发布的大模型评测社区,提供公正透明的LLM能力榜单、多领域评测集及Data Studio数据服务,助力AI模型性能评估与NLP科研开发。
    27次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    264次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码