ClickHouse EXPLAIN 完全实战指南:AST / SYNTAX / QUERY TREE / PLAN / PIPELINE 一文搞懂

摘要: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(执行流水线)

一句话记忆

类型回答的问题
ASTSQL 被解析成了什么结构?
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;

输出特点

  • 树形结构,节点类型如SelectQueryTablesInSelectQueryIdentifierFunction
  • 能看到 SELECT 列表、FROM、WHERE、GROUP BY、ORDER BY 在语法树上的位置
  • 不涉及物理执行、不涉及读多少行

怎么读

  1. 找到根节点SelectQuery
  2. 向下看TablesInSelectQuery→ 确认表名、别名
  3. 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;

输出特点

  • 树形逻辑算子:JOINFILTERAGGREGATESORTLIMIT
  • 比 SYNTAX 更接近执行,但还不是ReadFromMergeTree那种物理算子
  • 不同 ClickHouse 版本节点命名可能略有差异

怎么读

  1. 自顶向下:最外层通常是 LIMIT / SORT
  2. 中间层:AGGREGATE(聚合)、FILTER(过滤)
  3. 底层:表扫描相关的逻辑节点

和 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是否在内存中排序大量数据
LimitLIMIT 是否下推、是否 preliminary

实战口诀

先找最底层的ReadFromMergeTree,再往上数FilterAggregatingSorting

如果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 链
  • 可能出现MergeTreeSelectExpressionTransformAggregatingTransform
  • 信息量大,日常优化不建议从这里入手

可选:图形化输出(部分版本支持):

EXPLAINPIPELINE graph=1SELECT...;

和 PLAN 的关系

PLAN → 「做什么」:读表、过滤、聚合、排序 PIPELINE → 「怎么做」:Processor 如何串联、数据如何流动

四、五种类型对比速查表

类型编译阶段输出形态日常频率主要用途
AST解析语法树★☆☆☆☆语法结构、解析调试
SYNTAXAST 优化后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 里若看到:大范围ReadFromMergeTreeFilter在扫描之后 → 分区/主键没用上。

方向:改写为event_date范围条件,对齐PARTITION BYORDER 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、执行计划追问优化建议

推荐使用方式

  1. 本地先用EXPLAIN PLAN跑一遍,熟悉 raw 输出
  2. 把 SQL + EXPLAIN 结果粘贴到 https://ch.charunion.com 离线模式
  3. 在「执行计划」「诊断」「AI 助手」Tab 里继续深挖
  4. 测试环境可开连接模式,自动拉 query_log

访客可直接分析;登录后可保存历史记录、使用 AI 助手。


八、版本差异与注意事项

  1. 不同 ClickHouse 版本,EXPLAIN 节点名称和 JSON 字段可能略有不同,以你集群版本为准。
  2. 分布式表的 PLAN 会更复杂,注意ReadFromRemoteUnion等节点。
  3. EXPLAIN 不执行完整查询(PLAN 模式),但仍有解析和计划开销,避免对超大 SQL 频繁执行。
  4. JSON PLAN适合程序消费;人工阅读优先用文本 PLAN。
  5. 生产环境若不能直连,用离线模式把 EXPLAIN 结果复制到工具里分析即可。

九、总结

你想知道…用这个
SQL 怎么被解析的EXPLAIN AST
SQL 被改写成啥了EXPLAIN SYNTAX
逻辑算子怎么组织的EXPLAIN QUERY TREE
怎么读表、怎么跑EXPLAIN PLAN
Processor 怎么流水线执行EXPLAIN PIPELINE

日常口诀PLAN看计划,query_log看结果,对不上就查SYNTAXQUERY TREE

如果你希望少在终端和文档之间来回切换,可以直接用可视化工具辅助:

👉https://ch.charunion.com

欢迎在评论区交流你遇到的 EXPLAIN 输出案例,后续可以专门写一篇「PLAN 节点大全」。