摘要:ClickHouse 提供 5 种 EXPLAIN 类型,分别对应查询编译的不同阶段。很多同学只会
EXPLAIN PLAN,遇到「SQL 被改写了」「优化器做了什么」就无从下手。本文按可上手操作的方式,逐类型讲解用法、输出解读和排查场景,并附一套慢查询分析流程。配套工具:执行计划可视化 + AI 调优 → https://ch.charunion.com
关键词:ClickHouse、EXPLAIN、AST、SYNTAX、QUERY TREE、PLAN、PIPELINE、慢查询、SQL 优化
一、EXPLAIN 是什么?先建立整体认知
在 ClickHouse 里,EXPLAIN不是「估算成本」那么简单(和 Oracle/MySQL 的 EXPLAIN 习惯不完全一样),它更像一条查询编译透视链:
原始 SQL ↓ 解析 AST(抽象语法树) ↓ AST 级优化 SYNTAX(优化后 SQL 文本) ↓ Query Tree 级优化 QUERY TREE(逻辑查询树) ↓ 物理计划生成 PLAN(执行计划) ↓ 流水线展开 PIPELINE(执行流水线)一句话记忆:
| 类型 | 回答的问题 |
|---|---|
| AST | SQL 被解析成了什么结构? |
| SYNTAX | 优化器在语法层把 SQL 改成了什么样? |
| QUERY TREE | 逻辑算子树长什么样? |
| PLAN | 最终怎么读表、怎么聚合?(最常用) |
| PIPELINE | 底层 Processor 怎么串起来?(最深) |
二、准备:统一测试 SQL 和表结构
下文所有示例共用一张表,复制即可跑。
2.1 建表
CREATEDATABASEIFNOTEXISTSdemo;CREATETABLEIFNOTEXISTSdemo.events(event_dateDate,event_timeDateTime,user_id UInt64,event_type String,properties String)ENGINE=MergeTree()PARTITIONBYtoYYYYMM(event_date)ORDERBY(event_date,event_time,user_id);2.2 测试 SQL
SELECTevent_type,count()AScnt,uniq(user_id)ASuvFROMdemo.eventsWHEREevent_date>=today()-7GROUPBYevent_typeORDERBYcntDESCLIMIT10;2.3 基本语法格式
EXPLAIN[AST|SYNTAX|QUERY TREE|PLAN|PIPELINE][选项]<你的SELECT语句>在clickhouse-client或 HTTP 接口(8123)均可执行。多行 SQL 时建议去掉末尾分号,或整体包在子查询里。
三、五种 EXPLAIN 类型详解(附操作命令)
3.1 AST — 抽象语法树(Abstract Syntax Tree)
作用:查看 SQL解析后的语法树结构,对应编译最早期。
什么时候用:
- 怀疑 SQL 写法有歧义,想看 ClickHouse 怎么「理解」你的语句
- 调试宏、复杂嵌套子查询的解析结果
- 语法报错时,定位解析阶段问题
命令:
EXPLAINASTSELECTevent_type,count()AScnt,uniq(user_id)ASuvFROMdemo.eventsWHEREevent_date>=today()-7GROUPBYevent_typeORDERBYcntDESCLIMIT10;输出特点:
- 树形结构,节点类型如
SelectQuery、TablesInSelectQuery、Identifier、Function等 - 能看到 SELECT 列表、FROM、WHERE、GROUP BY、ORDER BY 在语法树上的位置
- 不涉及物理执行、不涉及读多少行
怎么读:
- 找到根节点
SelectQuery - 向下看
TablesInSelectQuery→ 确认表名、别名 - 看
ExpressionList→ 确认列和函数是否按预期解析
局限:AST 离「性能」还远,不能用它判断慢不慢。
3.2 SYNTAX — AST 级优化后的 SQL 文本
作用:展示经过AST 层优化之后的查询文本。
什么时候用:
- 发现「我写的 SQL」和「实际执行的 SQL」不一样
- 排查常量折叠、谓词下推前的语法改写
- 对比改写前后,理解优化器在语法层做了什么
命令:
EXPLAINSYNTAXSELECTevent_type,count()AScnt,uniq(user_id)ASuvFROMdemo.eventsWHEREevent_date>=today()-7GROUPBYevent_typeORDERBYcntDESCLIMIT10;输出特点:
- 通常返回一段可读的 SQL 文本(不是树)
- 可能合并了部分表达式、规范了函数写法
- 仍属于较「逻辑」的层面,不是执行计划
实战技巧:
把EXPLAIN SYNTAX的输出复制出来,和原始 SQL 做 diff:
- 日期条件是否被折叠成具体值
- 别名、函数是否被替换
- 子查询是否被展开或改写
示例场景:
-- 原始SELECTcount()FROMdemo.eventsWHEREevent_date>=today()-7;-- EXPLAIN SYNTAX 可能显示类似(具体因版本而异):-- SELECT count() FROM demo.events WHERE event_date >= toDate('2026-06-23')这说明today() - 7在语法优化阶段已被求值。
3.3 QUERY TREE — Query Tree 级优化后的查询树
作用:展示Query Tree 层优化之后的逻辑查询树,介于「SQL 文本」和「物理计划」之间。
什么时候用:
- 想看 JOIN、子查询、聚合在逻辑层如何组织
- PLAN 看不懂时,回退一层看逻辑结构
- 分析 CTE、UNION、子查询合并等行为
命令:
EXPLAINQUERY TREESELECTevent_type,count()AScnt,uniq(user_id)ASuvFROMdemo.eventsWHEREevent_date>=today()-7GROUPBYevent_typeORDERBYcntDESCLIMIT10;输出特点:
- 树形逻辑算子:
JOIN、FILTER、AGGREGATE、SORT、LIMIT等 - 比 SYNTAX 更接近执行,但还不是
ReadFromMergeTree那种物理算子 - 不同 ClickHouse 版本节点命名可能略有差异
怎么读:
- 自顶向下:最外层通常是 LIMIT / SORT
- 中间层:AGGREGATE(聚合)、FILTER(过滤)
- 底层:表扫描相关的逻辑节点
和 PLAN 的分工:
- QUERY TREE:逻辑上「先过滤还是先聚合」
- PLAN:物理上「从哪张表、用什么方式读」
3.4 PLAN — 查询执行计划(最常用)
作用:展示物理执行计划,是日常慢查询分析的首选。
什么时候用:
- 慢查询排查(80% 的场景用这个就够)
- 看是否全表扫描、分区是否裁剪
- 看聚合、排序、JOIN 发生在哪一层
- 和
system.query_log对照「计划 vs 实际」
命令(文本格式,推荐入门):
EXPLAINPLANSELECTevent_type,count()AScnt,uniq(user_id)ASuvFROMdemo.eventsWHEREevent_date>=today()-7GROUPBYevent_typeORDERBYcntDESCLIMIT10;典型输出(示意):
Expression (Project names) Limit (preliminary LIMIT (without OFFSET)) Sorting (Sorting for ORDER BY) Expression ((Before ORDER BY + (Projection + (WHERE + (PREWHERE ...))))) Aggregating Expression (Before GROUP BY) Filter (WHERE) ReadFromMergeTree (demo.events)命令(JSON 格式,适合程序解析):
EXPLAINPLANjson=1,description=1SELECTevent_type,count()AScnt,uniq(user_id)ASuvFROMdemo.eventsWHEREevent_date>=today()-7GROUPBYevent_typeORDERBYcntDESCLIMIT10;重点看什么:
| 节点 | 关注点 |
|---|---|
ReadFromMergeTree | 读哪张表、预估/实际行数、是否命中分区 |
Filter/PREWHERE | 过滤条件下推情况 |
Aggregating | 聚合是否在大结果集上进行 |
Sorting | 是否在内存中排序大量数据 |
Limit | LIMIT 是否下推、是否 preliminary |
实战口诀:
先找最底层的
ReadFromMergeTree,再往上数Filter→Aggregating→Sorting。
如果Aggregating上面还有巨大的ReadFromMergeTree且行数很高,优先怀疑:条件没下推、分区没裁剪、ORDER BY 键不合理。
配合 query_log:
-- 先真正执行一次SELECT...;-- 再查最近一次执行的指标SELECTquery_duration_ms,read_rows,read_bytes,memory_usage,queryFROMsystem.query_logWHEREtype='QueryFinish'ANDqueryLIKE'%demo.events%'ORDERBYevent_timeDESCLIMIT5;EXPLAIN 是计划,query_log 是结果,两者必须对照看。
3.5 PIPELINE — 查询执行流水线
作用:展示查询的Pipeline 执行图,细化到 Processor(处理器)级别,比 PLAN 更底层。
什么时候用:
- PLAN 不够用,想看多线程、流水线并行怎么组织
- 排查特定算子实现、数据流端口(Port)连接
- 深度性能调优、向 ClickHouse 社区报 Bug 时附材料
命令:
EXPLAINPIPELINESELECTevent_type,count()AScnt,uniq(user_id)ASuvFROMdemo.eventsWHEREevent_date>=today()-7GROUPBYevent_typeORDERBYcntDESCLIMIT10;输出特点:
- 编号步骤
(1)、(2)… 描述 Processor 链 - 可能出现
MergeTreeSelect、ExpressionTransform、AggregatingTransform等 - 信息量大,日常优化不建议从这里入手
可选:图形化输出(部分版本支持):
EXPLAINPIPELINE graph=1SELECT...;和 PLAN 的关系:
PLAN → 「做什么」:读表、过滤、聚合、排序 PIPELINE → 「怎么做」:Processor 如何串联、数据如何流动四、五种类型对比速查表
| 类型 | 编译阶段 | 输出形态 | 日常频率 | 主要用途 |
|---|---|---|---|---|
| AST | 解析 | 语法树 | ★☆☆☆☆ | 语法结构、解析调试 |
| SYNTAX | AST 优化后 | SQL 文本 | ★★☆☆☆ | 看 SQL 被改写成什么样 |
| QUERY TREE | 逻辑优化后 | 逻辑算子树 | ★★★☆☆ | 逻辑层 JOIN/聚合/子查询 |
| PLAN | 物理计划 | 树 / JSON | ★★★★★ | 慢查询分析主力 |
| PIPELINE | 流水线 | Processor 链 | ★★☆☆☆ | 深度调优、源码级排查 |
五、推荐上手流程:5 步排查慢查询
假设有一条生产慢 SQL,按这个顺序操作,不容易走弯路。
第 1 步:EXPLAIN PLAN(必做)
EXPLAINPLAN<你的慢SQL>;确认:扫描节点、过滤位置、聚合/排序层级。
第 2 步:查 query_log(必做)
SELECTevent_time,query_duration_ms,read_rows,read_bytes,memory_usage,tables,queryFROMsystem.query_logWHEREtype='QueryFinish'ANDquery_duration_ms>1000ORDERBYevent_timeDESCLIMIT20;确认:实际读了多少行、耗时多久。
第 3 步:结果和预期不符时 → EXPLAIN SYNTAX
看 SQL 是否被改写,条件是否被折叠或下推失败。
第 4 步:PLAN 逻辑看不懂时 → EXPLAIN QUERY TREE
回退到逻辑层,理清 JOIN / 子查询 / 聚合顺序。
第 5 步:仍无法定位 → EXPLAIN PIPELINE 或抓 profile
仅在前四步不够时再下钻,避免信息过载。
日常优化:PLAN + query_log 占 90% 语法疑惑:补 SYNTAX 结构复杂:补 QUERY TREE 底层深挖:PIPELINE六、三个常见实战案例
案例 1:条件没下推,读行数爆炸
现象:read_rows上亿,query_duration_ms很高。
操作:
EXPLAINPLANSELECTcount()FROMdemo.eventsWHEREtoYYYYMM(event_date)=202506;PLAN 里若看到:大范围ReadFromMergeTree,Filter在扫描之后 → 分区/主键没用上。
方向:改写为event_date范围条件,对齐PARTITION BY和ORDER BY。
案例 2:聚合前数据量过大
现象:Aggregating节点上方读取行数极高。
操作:
EXPLAINPLANSELECTuser_id,count()FROMdemo.eventsGROUPBYuser_id;方向:
- 增加 PREWHERE / WHERE 缩小范围
- 考虑物化视图预聚合
- 检查
GROUP BY基数是否过高
案例 3:ORDER BY 导致大量排序
现象:Sorting节点存在,且read_rows大。
操作:
EXPLAINPLANSELECT*FROMdemo.eventsORDERBYevent_typeLIMIT100;方向:
ORDER BY列不在排序键上时,必然大排序- 评估是否可改写法或加投影/索引(视版本而定)
七、命令行 vs 在线工具:怎么选?
命令行(clickhouse-client)
优点:生产环境直接查、可脚本化
缺点:树形输出难读、多类型切换麻烦、要和 query_log 手动对照
在线可视化工具
如果经常要在团队里分享分析结果,可以借助可视化工具减少手工整理。
我近期做了一个ClickHouse EXPLAIN 可视化 & AI 调优助手,把上面几种 EXPLAIN 类型和慢查询分析流程做成了「可点击」的操作:
地址:https://ch.charunion.com
能帮你做什么:
| 功能 | 说明 |
|---|---|
| 五种 EXPLAIN 类型 | AST / SYNTAX / QUERY TREE / PLAN / PIPELINE 一键切换 |
| 离线模式 | 粘贴已有 EXPLAIN 结果,无需连库 |
| 连接模式 | 直连 ClickHouse,自动执行 EXPLAIN |
| 计划树可视化 | 把 PLAN 输出解析成可折叠树形图 |
| query_log 关联 | 扫描节点挂载真实 read_rows、耗时等指标 |
| AI 调优对话 | 结合 SQL、DDL、执行计划追问优化建议 |
推荐使用方式:
- 本地先用
EXPLAIN PLAN跑一遍,熟悉 raw 输出 - 把 SQL + EXPLAIN 结果粘贴到 https://ch.charunion.com 离线模式
- 在「执行计划」「诊断」「AI 助手」Tab 里继续深挖
- 测试环境可开连接模式,自动拉 query_log
访客可直接分析;登录后可保存历史记录、使用 AI 助手。
八、版本差异与注意事项
- 不同 ClickHouse 版本,EXPLAIN 节点名称和 JSON 字段可能略有不同,以你集群版本为准。
- 分布式表的 PLAN 会更复杂,注意
ReadFromRemote、Union等节点。 - EXPLAIN 不执行完整查询(PLAN 模式),但仍有解析和计划开销,避免对超大 SQL 频繁执行。
- JSON PLAN适合程序消费;人工阅读优先用文本 PLAN。
- 生产环境若不能直连,用离线模式把 EXPLAIN 结果复制到工具里分析即可。
九、总结
| 你想知道… | 用这个 |
|---|---|
| SQL 怎么被解析的 | EXPLAIN AST |
| SQL 被改写成啥了 | EXPLAIN SYNTAX |
| 逻辑算子怎么组织的 | EXPLAIN QUERY TREE |
| 怎么读表、怎么跑 | EXPLAIN PLAN⭐ |
| Processor 怎么流水线执行 | EXPLAIN PIPELINE |
日常口诀:PLAN看计划,query_log看结果,对不上就查SYNTAX和QUERY TREE。
如果你希望少在终端和文档之间来回切换,可以直接用可视化工具辅助:
👉https://ch.charunion.com
欢迎在评论区交流你遇到的 EXPLAIN 输出案例,后续可以专门写一篇「PLAN 节点大全」。