Doris 提供了一个图形化的命令以帮助用户更方便的分析一个具体的查询或导入。本文介绍如何使用该功能。
查询计划树
SQL 是一个描述性语言,用户通过一个 SQL 来描述想获取的数据。而一个 SQL 的具体执行方式依赖于数据库的实现。而查询规划器就是用来决定数据库如何具体执行一个 SQL 的。
比如用户指定了一个 Join 算子,则查询规划器需要决定具体的 Join 算法,比如是 Hash Join,还是 Merge Sort Join;是使用 Shuffle 还是 Broadcast;Join 顺序是否需要调整以避免笛卡尔积;以及确定最终的在哪些节点执行等等。
Doris 的查询规划过程是先将一个 SQL 语句转换成一个单机执行计划树。
1. ┌────┐
2. │Sort│
3. └────┘
4. │
5. ┌───────────┐
6. │Aggregation│
7. └───────────┘
8. │
9. ┌────┐
10. │Join│
11. └────┘
12. ┌───┴────┐
13. ┌──────┐ ┌──────┐
14. │Scan-1│ │Scan-2│
15. └──────┘ └──────┘
之后,查询规划器会根据具体的算子执行方式、数据的具体分布,将单机查询计划转换为分布式查询计划。分布式查询计划是由多个 Fragment 组成的,每个 Fragment 负责查询计划的一部分,各个 Fragment 之间会通过 ExchangeNode 算子进行数据的传输。
1. ┌────┐
2. │Sort│
3. │F1 │
4. └────┘
5. │
6. ┌───────────┐
7. │Aggregation│
8. │F1 │
9. └───────────┘
10. │
11. ┌────┐
12. │Join│
13. │F1 │
14. └────┘
15. ┌──────┴────┐
16. ┌──────┐ ┌────────────┐
17. │Scan-1│ │ExchangeNode│
18. │F1 │ │F1 │
19. └──────┘ └────────────┘
20. │
21. ┌──────────────┐
22. │DataStreamSink│
23. │F2 │
24. └──────────────┘
25. │
26. ┌──────┐
27. │Scan-2│
28. │F2 │
29. └──────┘
如上图,我们将单机计划分成了两个 Fragment:F1 和 F2。两个 Fragment 之间通过一个 ExchangeNode 节点传输数据。
而一个 Fragment 会进一步的划分为多个 Instance。Instance 是最终具体的执行实例。划分成多个 Instance 有助于充分利用机器资源,提升一个 Fragment 的执行并发度。
查看查询计划
可以通过以下三种命令查看一个 SQL 的执行计划。
EXPLAIN GRAPH select ...;或者DESC GRAPH select ...;:这些命令提供了执行计划的图形表示。它们帮助我们可视化查询执行的流程,包括关联路径和数据访问方法。EXPLAIN select ...;:这个命令显示指定 SQL 查询的执行计划的文本表示形式。它提供了有关查询优化步骤的信息,例如操作的顺序、执行算法和访问方法等。EXPLAIN VERBOSE select ...;:与前一个命令类似,这个命令提供了更详细的输出结果。EXPLAIN PARSED PLAN select ...;:这个命令返回 SQL 查询的解析后的执行计划。它显示了计划树和查询处理中涉及的逻辑操作符的信息。EXPLAIN ANALYZED PLAN select ...;:这个命令返回 SQL 查询的分析后的执行计划。EXPLAIN REWRITTEN PLAN select ...;:这个命令在数据库引擎对查询进行任何查询转换或优化后显示了重写后的执行计划。它提供了查询为了提高性能而进行的修改的见解。EXPLAIN OPTIMIZED PLAN select ...;这个命令返回了 CBO 中得到的最优计划EXPLAIN SHAPE PLAN select ...;:这个命令以查询的形状和结构为重点,呈现了简化后的最优执行计划。
其中第一个命令以图形化的方式展示一个查询计划,这个命令可以比较直观的展示查询计划的树形结构,以及 Fragment 的划分情况:
1. mysql> explain graph select tbl1.k1, sum(tbl1.k2) from tbl1 join tbl2 on tbl1.k1 = tbl2.k1 group by tbl1.k1 order by tbl1.k1;
2. +---------------------------------------------------------------------------------------------------------------------------------+
3. | Explain String |
4. +---------------------------------------------------------------------------------------------------------------------------------+
5. | |
6. | ┌───────────────┐ |
7. | │[9: ResultSink]│ |
8. | │[Fragment: 4] │ |
9. | │RESULT SINK │ |
10. | └───────────────┘ |
11. | │ |
12. | ┌─────────────────────┐ |
13. | │[9: MERGING-EXCHANGE]│ |
14. | │[Fragment: 4] │ |
15. | └─────────────────────┘ |
16. | │ |
17. | ┌───────────────────┐ |
18. | │[9: DataStreamSink]│ |
19. | │[Fragment: 3] │ |
20. | │STREAM DATA SINK │ |
21. | │ EXCHANGE ID: 09 │ |
22. | │ UNPARTITIONED │ |
23. | └───────────────────┘ |
24. | │ |
25. | ┌─────────────┐ |
26. | │[4: TOP-N] │ |
27. | │[Fragment: 3]│ |
28. | └─────────────┘ |
29. | │ |
30. | ┌───────────────────────────────┐ |
31. | │[8: AGGREGATE (merge finalize)]│ |
32. | │[Fragment: 3] │ |
33. | └───────────────────────────────┘ |
34. | │ |
35. | ┌─────────────┐ |
36. | │[7: EXCHANGE]│ |
37. | │[Fragment: 3]│ |
38. | └─────────────┘ |
39. | │ |
40. | ┌───────────────────┐ |
41. | │[7: DataStreamSink]│ |
42. | │[Fragment: 2] │ |
43. | │STREAM DATA SINK │ |
44. | │ EXCHANGE ID: 07 │ |
45. | │ HASH_PARTITIONED │ |
46. | └───────────────────┘ |
47. | │ |
48. | ┌─────────────────────────────────┐ |
49. | │[3: AGGREGATE (update serialize)]│ |
50. | │[Fragment: 2] │ |
51. | │STREAMING │ |
52. | └─────────────────────────────────┘ |
53. | │ |
54. | ┌─────────────────────────────────┐ |
55. | │[2: HASH JOIN] │ |
56. | │[Fragment: 2] │ |
57. | │join op: INNER JOIN (PARTITIONED)│ |
58. | └─────────────────────────────────┘ |
59. | ┌──────────┴──────────┐ |
60. | ┌─────────────┐ ┌─────────────┐ |
61. | │[5: EXCHANGE]│ │[6: EXCHANGE]│ |
62. | │[Fragment: 2]│ │[Fragment: 2]│ |
63. | └─────────────┘ └─────────────┘ |
64. | │ │ |
65. | ┌───────────────────┐ ┌───────────────────┐ |
66. | │[5: DataStreamSink]│ │[6: DataStreamSink]│ |
67. | │[Fragment: 0] │ │[Fragment: 1] │ |
68. | │STREAM DATA SINK │ │STREAM DATA SINK │ |
69. | │ EXCHANGE ID: 05 │ │ EXCHANGE ID: 06 │ |
70. | │ HASH_PARTITIONED │ │ HASH_PARTITIONED │ |
71. | └───────────────────┘ └───────────────────┘ |
72. | │ │ |
73. | ┌─────────────────┐ ┌─────────────────┐ |
74. | │[0: OlapScanNode]│ │[1: OlapScanNode]│ |
75. | │[Fragment: 0] │ │[Fragment: 1] │ |
76. | │TABLE: tbl1 │ │TABLE: tbl2 │ |
77. | └─────────────────┘ └─────────────────┘ |
78. +---------------------------------------------------------------------------------------------------------------------------------+
从图中可以看出,查询计划树被分为了 5 个 Fragment:0、1、2、3、4。如 OlapScanNode 节点上的 [Fragment: 0] 表示这个节点属于 Fragment 0。每个 Fragment 之间都通过 DataStreamSink 和 ExchangeNode 进行数据传输。
图形命令仅展示简化后的节点信息,如果需要查看更具体的节点信息,如下推到节点上的过滤条件等,则需要通过第二个命令查看更详细的文字版信息:
1. mysql> explain select tbl1.k1, sum(tbl1.k2) from tbl1 join tbl2 on tbl1.k1 = tbl2.k1 group by tbl1.k1 order by tbl1.k1;
2. +----------------------------------------------------------------------------------+
3. | Explain String |
4. +----------------------------------------------------------------------------------+
5. | PLAN FRAGMENT 0 |
6. | OUTPUT EXPRS:<slot 5> <slot 3> `tbl1`.`k1` | <slot 6> <slot 4> sum(`tbl1`.`k2`) |
7. | PARTITION: UNPARTITIONED |
8. | |
9. | RESULT SINK |
10. | |
11. | 9:MERGING-EXCHANGE |
12. | limit: 65535 |
13. | |
14. | PLAN FRAGMENT 1 |
15. | OUTPUT EXPRS: |
16. | PARTITION: HASH_PARTITIONED: <slot 3> `tbl1`.`k1` |
17. | |
18. | STREAM DATA SINK |
19. | EXCHANGE ID: 09 |
20. | UNPARTITIONED |
21. | |
22. | 4:TOP-N |
23. | | order by: <slot 5> <slot 3> `tbl1`.`k1` ASC |
24. | | offset: 0 |
25. | | limit: 65535 |
26. | | |
27. | 8:AGGREGATE (merge finalize) |
28. | | output: sum(<slot 4> sum(`tbl1`.`k2`)) |
29. | | group by: <slot 3> `tbl1`.`k1` |
30. | | cardinality=-1 |
31. | | |
32. | 7:EXCHANGE |
33. | |
34. | PLAN FRAGMENT 2 |
35. | OUTPUT EXPRS: |
36. | PARTITION: HASH_PARTITIONED: `tbl1`.`k1` |
37. | |
38. | STREAM DATA SINK |
39. | EXCHANGE ID: 07 |
40. | HASH_PARTITIONED: <slot 3> `tbl1`.`k1` |
41. | |
42. | 3:AGGREGATE (update serialize) |
43. | | STREAMING |
44. | | output: sum(`tbl1`.`k2`) |
45. | | group by: `tbl1`.`k1` |
46. | | cardinality=-1 |
47. | | |
48. | 2:HASH JOIN |
49. | | join op: INNER JOIN (PARTITIONED) |
50. | | runtime filter: false |
51. | | hash predicates: |
52. | | colocate: false, reason: table not in the same group |
53. | | equal join conjunct: `tbl1`.`k1` = `tbl2`.`k1` |
54. | | cardinality=2 |
55. | | |
56. | |----6:EXCHANGE |
57. | | |
58. | 5:EXCHANGE |
59. | |
60. | PLAN FRAGMENT 3 |
61. | OUTPUT EXPRS: |
62. | PARTITION: RANDOM |
63. | |
64. | STREAM DATA SINK |
65. | EXCHANGE ID: 06 |
66. | HASH_PARTITIONED: `tbl2`.`k1` |
67. | |
68. | 1:OlapScanNode |
69. | TABLE: tbl2 |
70. | PREAGGREGATION: ON |
71. | partitions=1/1 |
72. | rollup: tbl2 |
73. | tabletRatio=3/3 |
74. | tabletList=105104776,105104780,105104784 |
75. | cardinality=1 |
76. | avgRowSize=4.0 |
77. | numNodes=6 |
78. | |
79. | PLAN FRAGMENT 4 |
80. | OUTPUT EXPRS: |
81. | PARTITION: RANDOM |
82. | |
83. | STREAM DATA SINK |
84. | EXCHANGE ID: 05 |
85. | HASH_PARTITIONED: `tbl1`.`k1` |
86. | |
87. | 0:OlapScanNode |
88. | TABLE: tbl1 |
89. | PREAGGREGATION: ON |
90. | partitions=1/1 |
91. | rollup: tbl1 |
92. | tabletRatio=3/3 |
93. | tabletList=105104752,105104763,105104767 |
94. | cardinality=2 |
95. | avgRowSize=8.0 |
96. | numNodes=6 |
97. +----------------------------------------------------------------------------------+
第三个命令EXPLAIN VERBOSE select ...;相比第二个命令可以查看更详细的执行计划信息。
``
1. mysql> explain verbose select tbl1.k1, sum(tbl1.k2) from tbl1 join tbl2 on tbl1.k1 = tbl2.k1 group by tbl1.k1 order by tbl1.k1;
2. +---------------------------------------------------------------------------------------------------------------------------------------------------------+
3. | Explain String |
4. +---------------------------------------------------------------------------------------------------------------------------------------------------------+
5. | PLAN FRAGMENT 0 |
6. | OUTPUT EXPRS:<slot 5> <slot 3>tbl1.k1| <slot 6> <slot 4> sum(tbl1.k2) |
7. | PARTITION: UNPARTITIONED |
8. | |
9. | VRESULT SINK |
10. | |
11. | 6:VMERGING-EXCHANGE |
12. | limit: 65535 |
13. | tuple ids: 3 |
14. | |
15. | PLAN FRAGMENT 1 |
16. | |
17. | PARTITION: HASH_PARTITIONED:default_cluster:test.tbl1.k2|
18. | |
19. | STREAM DATA SINK |
20. | EXCHANGE ID: 06 |
21. | UNPARTITIONED |
22. | |
23. | 4:VTOP-N |
24. | | order by: <slot 5> <slot 3>tbl1.k1ASC |
25. | | offset: 0 |
26. | | limit: 65535 |
27. | | tuple ids: 3 |
28. | | |
29. | 3:VAGGREGATE (update finalize) |
30. | | output: sum(<slot 8>) |
31. | | group by: <slot 7> |
32. | | cardinality=-1 |
33. | | tuple ids: 2 |
34. | | |
35. | 2:VHASH JOIN |
36. | | join op: INNER JOIN(BROADCAST)[Tables are not in the same group] |
37. | | equal join conjunct: CAST(tbl1.k1AS DATETIME) =tbl2.k1|
38. | | runtime filters: RF000[in_or_bloom] <-tbl2.k1|
39. | | cardinality=0 |
40. | | vec output tuple id: 4 | tuple ids: 0 1 |
41. | | |
42. | |----5:VEXCHANGE |
43. | | tuple ids: 1 |
44. | | |
45. | 0:VOlapScanNode |
46. | TABLE: tbl1(null), PREAGGREGATION: OFF. Reason: the type of agg on StorageEngine's Key column should only be MAX or MIN.agg expr: sum(tbl1.k2) |
47. | runtime filters: RF000[in_or_bloom] -> CAST(tbl1.k1AS DATETIME) |
48. | partitions=0/1, tablets=0/0, tabletList= |
49. | cardinality=0, avgRowSize=20.0, numNodes=1 |
50. | tuple ids: 0 |
51. | |
52. | PLAN FRAGMENT 2 |
53. | |
54. | PARTITION: HASH_PARTITIONED:default_cluster:test.tbl2.k2` |
55. | |
56. | STREAM DATA SINK |
57. | EXCHANGE ID: 05 |
58. | UNPARTITIONED |
59. | |
60. | 1:VOlapScanNode |
61. | TABLE: tbl2(null), PREAGGREGATION: OFF. Reason: null |
62. | partitions=0/1, tablets=0/0, tabletList= |
63. | cardinality=0, avgRowSize=16.0, numNodes=1 |
64. | tuple ids: 1 |
65. | |
66. | Tuples: |
67. | TupleDescriptor{id=0, tbl=tbl1, byteSize=32, materialized=true} |
68. | SlotDescriptor{id=0, col=k1, type=DATE} |
69. | parent=0 |
70. | materialized=true |
71. | byteSize=16 |
72. | byteOffset=16 |
73. | nullIndicatorByte=0 |
74. | nullIndicatorBit=-1 |
75. | slotIdx=1 |
76. | |
77. | SlotDescriptor{id=2, col=k2, type=INT} |
78. | parent=0 |
79. | materialized=true |
80. | byteSize=4 |
81. | byteOffset=0 |
82. | nullIndicatorByte=0 |
83. | nullIndicatorBit=-1 |
84. | slotIdx=0 |
85. | |
86. | |
87. | TupleDescriptor{id=1, tbl=tbl2, byteSize=16, materialized=true} |
88. | SlotDescriptor{id=1, col=k1, type=DATETIME} |
89. | parent=1 |
90. | materialized=true |
91. | byteSize=16 |
92. | byteOffset=0 |
93. | nullIndicatorByte=0 |
94. | nullIndicatorBit=-1 |
95. | slotIdx=0 |
96. | |
97. | |
98. | TupleDescriptor{id=2, tbl=null, byteSize=32, materialized=true} |
99. | SlotDescriptor{id=3, col=null, type=DATE} |
100. | parent=2 |
101. | materialized=true |
102. | byteSize=16 |
103. | byteOffset=16 |
104. | nullIndicatorByte=0 |
105. | nullIndicatorBit=-1 |
106. | slotIdx=1 |
107. | |
108. | SlotDescriptor{id=4, col=null, type=BIGINT} |
109. | parent=2 |
110. | materialized=true |
111. | byteSize=8 |
112. | byteOffset=0 |
113. | nullIndicatorByte=0 |
114. | nullIndicatorBit=-1 |
115. | slotIdx=0 |
116. | |
117. | |
118. | TupleDescriptor{id=3, tbl=null, byteSize=32, materialized=true} |
119. | SlotDescriptor{id=5, col=null, type=DATE} |
120. | parent=3 |
121. | materialized=true |
122. | byteSize=16 |
123. | byteOffset=16 |
124. | nullIndicatorByte=0 |
125. | nullIndicatorBit=-1 |
126. | slotIdx=1 |
127. | |
128. | SlotDescriptor{id=6, col=null, type=BIGINT} |
129. | parent=3 |
130. | materialized=true |
131. | byteSize=8 |
132. | byteOffset=0 |
133. | nullIndicatorByte=0 |
134. | nullIndicatorBit=-1 |
135. | slotIdx=0 |
136. | |
137. | |
138. | TupleDescriptor{id=4, tbl=null, byteSize=48, materialized=true} |
139. | SlotDescriptor{id=7, col=k1, type=DATE} |
140. | parent=4 |
141. | materialized=true |
142. | byteSize=16 |
143. | byteOffset=16 |
144. | nullIndicatorByte=0 |
145. | nullIndicatorBit=-1 |
146. | slotIdx=1 |
147. |
(本文超出站点单文档体积上限,已截断)
