同一条 SELECT,用 EXPLAIN AST / PLAN / PIPELINE 加 query_log 拆开看它从文本到结果,经过了哪几层。
对同一条查询,用 ClickHouse 自带的三种 EXPLAIN 模式加上 query_log,看它经过了哪几层。
这条 SQL 全文用来做四次解剖SELECT id, v FROM demo.olap_batch WHERE id > 100 ORDER BY v DESC LIMIT 5
一条 SELECT 从文本变成结果,要经过:先被解析成语法树(AST),再被分析、优化成一份执行计划(Plan),再被展开成一组能并行跑的处理器(Pipeline),最后才是处理器真正读数据、算结果的执行阶段。下面四步分别是这条 SQL 在这四层的样子。
SQL 文本首先被解析成一棵抽象语法树,这一步还不知道 id 是不是有索引、v 是不是能排序优化。
EXPLAIN AST SELECT id, v FROM demo.olap_batch WHERE id > 100 ORDER BY v DESC LIMIT 5SelectWithUnionQuery (children 1) ExpressionList (children 1) SelectQuery (children 5) ExpressionList (children 2) Identifier id Identifier v TablesInSelectQuery (children 1) TableIdentifier demo.olap_batch Function greater (children 1) Identifier id Literal UInt64_100 OrderByElement Identifier v Literal UInt64_5
这棵树里 WHERE id > 100 就是一个普通的 Function greater 节点,跟 ORDER BY v DESC LIMIT 5 平级摆着——语法树只管"这句话说的是什么",完全不管"应该先做哪一步、能不能用索引跳过数据"。
语法树被分析器和优化器处理后,变成一棵自底向上执行的计划树。
EXPLAIN PLAN SELECT id, v FROM demo.olap_batch WHERE id > 100 ORDER BY v DESC LIMIT 5Limit (preliminary LIMIT) │ Limit 5 └──Sorting (Sorting for ORDER BY) │ Sort description: v DESC │ Limit 5 └──Filter ((WHERE + Change column names)) │ Filter column: id > 100 └──ReadFromMergeTree (demo.olap_batch) Read type: Default Parts: 1 | Granules: 1
这一层已经把顺序定下来了:先 ReadFromMergeTree 读数据,再 Filter 过滤 id > 100,再 Sorting 按 v 倒排,最后 Limit 只留 5 行。而且优化器已经把 LIMIT 5 下推到了排序步骤里("Sort description ... Limit 5"),排序的时候就不用对全部结果做完整排序。
执行计划的每一个节点会被展开成一个或多个"处理器"(Processor),这些处理器才是最终真正跑起来的东西。
EXPLAIN PIPELINE SELECT id, v FROM demo.olap_batch WHERE id > 100 ORDER BY v DESC LIMIT 5(Expression) ExpressionTransform (Limit) Limit (Sorting) MergeSortingTransform LimitsCheckingTransform PartialSortingTransform (Expression) ExpressionTransform (Filter) FilterTransform (Expression) ExpressionTransform (ReadFromMergeTree) MergeTreeSelect(pool: ReadPoolInOrder, algorithm: InOrder)
执行计划里的一个 Sorting 节点,展开成了三个处理器:PartialSortingTransform(对每个数据块局部排序)、LimitsCheckingTransform(检查资源限制)、MergeSortingTransform(把多个已排序的块归并成一个有序结果)。执行计划描述"要做什么",Pipeline 才是"具体由哪些处理器、按什么顺序真的去做"。
前三层都是"计划",只有真正跑起来才会在 system.query_log 里留下痕迹。
SELECT query_duration_ms, memory_usage, read_rows, result_rows FROM system.query_log WHERE type='QueryFinish' ...query_duration_ms: 4 memory_usage: 176177 read_rows: 200 result_rows: 5
执行只花了 4 毫秒,读了 200 行(因为 Granules: 1,这张小表整个只有 1 个索引颗粒,没法再细分跳过),最终返回 5 行,占用了约 172 KiB 内存。前面三层看到的 AST、Plan、Pipeline 都只是"计划应该怎么做",这一步的四个数字才是"真的做了多少"。
每一步都是本文上面用EXPLAIN和query_log拆出来的原始结果。
EXPLAIN AST / EXPLAIN PLAN / EXPLAIN PIPELINE:ClickHouse 自带的三种解剖视角system.query_log:执行后的耗时、内存、读取行数