Apache Doris执行计划:EXPLAIN输出详解
你是否在使用Apache Doris时遇到查询性能瓶颈?是否想优化SQL却不知从何下手?本文将带你深入理解EXPLAIN命令的输出结果,掌握执行计划分析技巧,轻松定位查询效率问题。读完本文,你将能够:
- 理解EXPLAIN输出的基本结构
- 识别常见的执行算子
- 学会分析执行计划并发现优化点
- 掌握使用Nereids Planner的高级技巧
什么是执行计划
执行计划(Execution Plan)是Apache Doris查询优化器为SQL语句生成的执行方案,它描述了查询将如何被执行,包括数据读取方式、算子选择、连接顺序等关键信息。通过EXPLAIN命令,我们可以查看执行计划,从而了解查询的内部执行流程,为性能优化提供依据。
在Apache Doris中,执行计划由一系列执行算子(Operator)组成,每个算子负责特定的数据处理任务。这些算子按一定的顺序组合,形成一个有向无环图(DAG),数据从下往上流动,经过各个算子的处理后得到最终结果。
如何生成执行计划
在Apache Doris中,使用EXPLAIN命令可以生成SQL语句的执行计划。基本语法如下:
EXPLAIN SELECT ...;
例如,我们可以对一个简单的查询生成执行计划:
EXPLAIN SELECT v1.k FROM test_view v1 LEFT JOIN test_view v2 ON v1.k = v2.k;
示例来源:pytest/qe/query_regression/sql/issue_4241.sql
此外,Doris还支持CTE(Common Table Expression)语法的执行计划展示:
EXPLAIN WITH test_view (k) AS ( SELECT NULL AS k UNION ALL SELECT NULL AS k )
SELECT v1.k FROM test_view v1 LEFT JOIN test_view v2 ON v1.k = v2.k;
示例来源:pytest/qe/query_regression/sql/issue_4241.sql
执行计划基本结构
Apache Doris的执行计划输出通常包含以下几个部分:
- ID:算子的唯一标识符
- OPERATOR:算子名称
- NAME:算子的具体名称或描述
- EST. ROWS:估计的输出行数
- EST. BYTES:估计的输出数据量
- PROPERTIES:算子的属性信息
下面是一个典型的执行计划输出示例:
+-------------------------------------------------------------------------------------------------+
| ID | OPERATOR | NAME | EST. ROWS | EST. BYTES | PROPERTIES |
+-------------------------------------------------------------------------------------------------+
| 0 | EXCHANGE | UNPARTITION | 10000 | 400000 | TYPE: GATHER |
| 1 | AGGREGATE | SUM | 10000 | 400000 | group by: k, sum: v |
| 2 | EXCHANGE | HASH | 100000 | 4000000 | HASH KEYS: k |
| 3 | AGGREGATE | PARTIAL_SUM| 100000 | 4000000 | group by: k, sum: v |
| 4 | SCAN | OLAP_TABLE | 1000000 | 40000000 | table: test, partitions: [p1], buckets: [1-10] |
+-------------------------------------------------------------------------------------------------+
这个执行计划展示了一个聚合查询的执行流程,从最底层的SCAN算子读取数据,到中间的AGGREGATE算子进行部分聚合,再通过EXCHANGE算子进行数据重分布,最后进行全局聚合并返回结果。
常见执行算子解析
1. SCAN算子
SCAN算子负责从存储引擎读取数据,是执行计划的起点。常见的SCAN算子有:
- OLAP_TABLE_SCAN:从Doris的OLAP表中读取数据
- MYSQL_SCAN:从MySQL外部表读取数据
- HIVE_SCAN:从Hive外部表读取数据
- ES_SCAN:从Elasticsearch外部表读取数据
SCAN算子的PROPERTIES中通常包含表名、分区信息、分桶信息等。例如:
PROPERTIES: {
"table": "test_table",
"partitions": ["p202301", "p202302"],
"buckets": ["1-10"],
"predicates": "dt >= '2023-01-01'"
}
2. AGGREGATE算子
AGGREGATE算子负责执行聚合操作,如COUNT、SUM、AVG等。常见的AGGREGATE算子有:
- PARTIAL_AGGREGATE:部分聚合,在数据分片上进行初步聚合
- FINAL_AGGREGATE:最终聚合,对部分聚合的结果进行全局聚合
- STREAMING_AGGREGATE:流式聚合,适用于已排序的数据
AGGREGATE算子的PROPERTIES中会显示聚合函数和分组键信息,例如:
PROPERTIES: {
"group by": "user_id",
"functions": "count(*), sum(amount)"
}
3. JOIN算子
JOIN算子负责执行表连接操作,常见的JOIN算子有:
- HASH_JOIN:哈希连接,适用于大表连接
- MERGE_JOIN:合并连接,适用于已排序的表连接
- NESTED_LOOP_JOIN:嵌套循环连接,适用于小表连接
JOIN算子的PROPERTIES中会显示连接类型、连接条件等信息,例如:
PROPERTIES: {
"join_type": "INNER JOIN",
"condition": "a.id = b.user_id",
"left_table": "orders",
"right_table": "users"
}
4. EXCHANGE算子
EXCHANGE算子负责在不同节点间传输数据,实现数据重分布。常见的EXCHANGE算子有:
- HASH_EXCHANGE:根据哈希值进行数据重分布
- RANGE_EXCHANGE:根据范围进行数据重分布
- BROADCAST_EXCHANGE:将数据广播到所有节点
- GATHER_EXCHANGE:将数据收集到一个节点
EXCHANGE算子的PROPERTIES中会显示数据重分布的方式和关键字,例如:
PROPERTIES: {
"type": "HASH",
"keys": "user_id"
}
Nereids Planner vs Legacy Planner
Apache Doris提供了两种查询优化器:传统优化器(Legacy Planner)和Nereids Planner。Nereids Planner是新一代的查询优化器,基于Cascades框架实现,具有更强大的优化能力。
如何切换Planner
可以通过会话变量切换使用的Planner:
-- 启用Nereids Planner
SET enable_nereids_planner = true;
-- 启用Legacy Planner
SET enable_nereids_planner = false;
也可以在单个查询中通过HINT指定使用的Planner:
-- 使用Nereids Planner
EXPLAIN SELECT /*+ SET_VAR(enable_nereids_planner=true) */ * FROM test_table;
-- 使用Legacy Planner
EXPLAIN SELECT /*+ SET_VAR(enable_nereids_planner=false) */ * FROM test_table;
示例来源:fe/fe-core/src/test/java/org/apache/doris/policy/PolicyTest.java
两种Planner的执行计划对比
Nereids Planner相比Legacy Planner在执行计划上有以下改进:
- 更准确的代价估算:基于统计信息的精确代价模型
- 更丰富的算子选择:支持更多类型的连接算法和聚合方式
- 更灵活的执行策略:能够根据数据分布动态调整执行计划
以下是使用两种Planner对同一SQL生成的执行计划对比:
Legacy Planner:
+--------------------------------------------------+
| ID | OPERATOR | NAME | EST. ROWS | ... |
+--------------------------------------------------+
| 0 | EXCHANGE | GATHER | 1000 | ... |
| 1 | AGGREGATE | SUM | 1000 | ... |
| 2 | EXCHANGE | HASH | 10000 | ... |
| 3 | AGGREGATE | PARTIAL_SUM| 10000 | ... |
| 4 | SCAN | OLAP_TABLE | 100000 | ... |
+--------------------------------------------------+
Nereids Planner:
+--------------------------------------------------+
| ID | OPERATOR | NAME | EST. ROWS | ... |
+--------------------------------------------------+
| 0 | PROJECT | | 1000 | ... |
| 1 | AGGREGATE | SUM | 1000 | ... |
| 2 | SCAN | OLAP_TABLE | 100000 | ... |
+--------------------------------------------------+
可以看到,Nereids Planner能够识别出该查询可以通过单节点完成,避免了不必要的数据交换,从而提高查询效率。
执行计划分析实战
案例1:识别全表扫描
全表扫描(Full Table Scan)是影响查询性能的常见问题。通过分析执行计划,我们可以快速识别是否存在全表扫描。
以下是一个包含全表扫描的执行计划片段:
| 4 | SCAN | OLAP_TABLE | 1000000 | 40000000 | table: test, partitions: [], buckets: [] |
在PROPERTIES中,如果partitions和buckets为空,通常表示发生了全表扫描。解决方法是添加合适的分区过滤条件或创建索引。
案例2:优化JOIN顺序
JOIN顺序对查询性能有很大影响。优化器通常会选择最优的JOIN顺序,但在某些情况下可能需要手动干预。
以下是一个包含多表JOIN的执行计划片段:
+--------------------------------------------------+
| ID | OPERATOR | NAME | EST. ROWS | ... |
+--------------------------------------------------+
| 0 | HASH_JOIN | | 1000 | ... |
| 1 | HASH_JOIN | | 10000 | ... |
| 2 | SCAN | TABLE_A | 100000 | ... |
| 3 | SCAN | TABLE_B | 10000 | ... |
| 4 | SCAN | TABLE_C | 1000 | ... |
+--------------------------------------------------+
这个执行计划显示先进行TABLE_A和TABLE_B的JOIN,再与TABLE_C进行JOIN。如果TABLE_C的数据量很小,我们可以通过HINT强制先进行TABLE_B和TABLE_C的JOIN,减少中间结果集大小:
EXPLAIN SELECT /*+ JOIN_ORDER(TABLE_B, TABLE_C, TABLE_A) */ ...
案例3:聚合优化
对于大数据量的聚合查询,部分聚合(Partial Aggregation)可以显著提高性能。通过执行计划,我们可以检查是否启用了部分聚合。
以下是一个包含部分聚合的执行计划片段:
+--------------------------------------------------+
| ID | OPERATOR | NAME | EST. ROWS | ... |
+--------------------------------------------------+
| 0 | AGGREGATE | FINAL | 1000 | ... |
| 1 | EXCHANGE | HASH | 10000 | ... |
| 2 | AGGREGATE | PARTIAL | 10000 | ... |
| 3 | SCAN | OLAP_TABLE | 100000 | ... |
+--------------------------------------------------+
如果执行计划中没有PARTIAL_AGGREGATE算子,可能是因为优化器认为部分聚合不会带来性能提升。这时可以通过HINT强制启用部分聚合:
EXPLAIN SELECT /*+ PARTIAL_AGGREGATION() */ ...
高级功能:执行计划可视化
Apache Doris提供了执行计划可视化功能,可以将EXPLAIN输出转换为图形化展示。这对于理解复杂的执行计划非常有帮助。
要使用执行计划可视化功能,可以在EXPLAIN命令后添加FORMAT参数:
EXPLAIN FORMAT=GRAPH SELECT ...;
执行后会生成一个SVG格式的执行计划图,展示算子之间的关系和数据流向。
总结
通过本文的介绍,相信你已经掌握了Apache Doris执行计划的基本概念、结构和分析方法。总结一下关键点:
- 使用EXPLAIN命令可以生成SQL语句的执行计划
- 执行计划由一系列算子组成,数据从下往上流动
- 常见的算子包括SCAN、AGGREGATE、JOIN和EXCHANGE等
- Nereids Planner提供了更优的执行计划生成能力
- 通过分析执行计划,可以识别和解决查询性能问题
希望本文能帮助你更好地理解和使用Apache Doris,编写出更高效的SQL查询。如果你想深入了解更多执行计划相关的知识,可以参考以下资源:
- 官方文档:README.md
- 测试用例:pytest/qe/query_regression/sql/
- 源码实现:fe/fe-core/src/main/java/org/apache/doris/planner/
祝你在Apache Doris的使用之旅中取得更好的性能和体验!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



