[HINT_EXPLAIN]
To monitor the real-time performance and execution order of a SQL query, you can print the query execution plan by adding "[HINT_EXPLAIN]" after the SQL keywords "select" or "exec". For example:
select [HINT_EXPLAIN] * from tb where id > 20
Note:
-
A SQL query with "[HINT_EXPLAIN]" returns a JSON string indicating the execution plan instead of the actual query result.
-
Currently, "[HINT_EXPLAIN]" does not work with "update" and "delete" statements.
"[HINT_EXPLAIN]" can be used in queries on both partitioned and non-partitioned tables. For a query on a partitioned table, the execution plan contains extra information about the map-reduce operation. This tutorial uses queries on a DFS table as examples to explain the attributes in the execution plan.
The outermost level of the JSON string consists of 2 root attributes,
measurement
and explain
.
measurement
"measurement":"microsecond"
This indicates that the elapsed time (i.e., the values of the cost
attribute) in the execution plan is measured in microseconds.
explain
The order of the sub attributes inside explain
, from top to bottom,
represents the execution order of the SQL query. Since a query may contain
subqueries, explain can also be nested inside other attributes.
explain
contains the following sub attributes:
-
rows
: the number of records returned -
cost
: the time it takes to execute the query
{
"measurement":"microsecond",
"explain": {
/* other attribute-value pairs */
"rows": 20000,
"cost": 100
}
}
The execution of a distributed query has 3 phases: map → merge → reduce. The
attributes corresponding to the 3 phases are usually nested inside the root
attribute explain
. For example:
{
"measurement":"microsecond",
"explain":{
"from":{...},
"map":{...},
"merge":{...},
"reduce":{...},
"rows":10,
"cost":23530
}
}
The system optimizes queries where possible and the associated information is included in the "optimize" attribute in the execution plan. For example, in standalone mode, create a DFS table partitioned on the "date" column. Query all the records in a single partition:
select [HINT_EXPLAIN] * from t1 where date = 2022.01.01
The query is optimized and an "optimize" attribute is included in the execution plan.
{
"measurement":"microsecond",
"explain":{
"from":{ ... },
"optimize":{ ... },
"rows":185,
"cost":987
}
}
from
from
indicates the execution plan of the parsed "from"
clause.
Depending on what follows the "from" keyword in the query, the
from
attribute may contain different sub attributes. Below
are some of the possible scenarios:
(1) "from" is followed by a table object. For example, select [HINT_EXPLAIN] * from pt
"from": {
"cost": 3 // Cost of the "from" clause
}
(2) The "from" clause contains a nested clause. For example, select [HINT_EXPLAIN] * from (select max(x) as maxx from loadTable("dfs://valuedb",`pt) group by month ) where maxx < 0.9994
"from": {
"cost": 33571, // Cost of the "from" clause
"detail": {
"sql": "select [98304] max(x) as maxx from loadTable("dfs://valuedb", "pt") group by month", // The nested clause
"explain": { ... } // "explain" for the nested clause
}
}
In this example, there's a nested explain
inside
from
showing the execution plan of the "select"
subquery.
(3) For version 2.00.12 and earlier, when "from" is followed by a join function, the "detail" attribute is returned, indicating whether or not the clause involves a table join operation. For example, select [HINT_EXPLAIN] * from lsj(pt1, pt2, "date")
"from": {
"cost": 3, // Cost of the "from" clause
"detail": "materialize for JOIN" // Indicates that the clause involves a table join
}
from
attribute will not be
returned for a join function following the "from" clause. Instead, the
materialization
attribute is introduced. The
left
and right
sub attributes are
added to display the subqueries for accessing source tables, i.e.,
leftTable and rightTable parameters of join functions.
For example, select [HINT_EXPLAIN] * from ej(t1, select * from pt2,
"id")."materialization":{
"cost": 37,
"left": "pt1", // leftTable
"right": "select * from pt2" // rightTable
}
Note: At this stage, the tables are not yet joined. cost
only indicates the cost to load the source tables "pt1" and "pt2".
JOIN
Starting from version 2.00.12, HINT_EXPLAIN supports displaying the detailed execution plan for joins, covering various table joiners: cross join, inner join, left join, left semi join, right join, and full join.
Note: The following cases DO NOT follow the new execution plan:
-
Any of the tables involved in the join are partitioned in-memory tables;
-
Joins using join functions with matchingCols set as partitioning columns;
-
Joins using
cj
function, excluding nestedcj
used as the leftTable in a query (without PIVOT BY). For example,select * from cj(select * from cj(select * from cj(t1, t2), t3), select * from t4) follows the new execution plan.
follows the new execution plan.
"sql": "...", // subquery for retrieving data
// returns "<Empty Segment>" if there is no corresponding partition
"rows": 10, // the number of rows obtained
"cost": 237, // cost to retrieve data
"resultPartitionInfo": {
"partitionParamsSet": true, // whether the join attempts to return a partitioned table. If true, the following info is returned.
"forcePartition": true, // whether to forcibly generate a partitioned table
"domainType": "NULL/VALUE/HASH/...", // partition type. If no such partitions, returns NULL.
"domainScheme": [1, 2, 3], // partitioning scheme for non-NULL "domainType"
"partitionColumns": ["col1", "col2"], // the partitioning columns
"useSeqPartition": false // whether the returned table is SEQ-partitioned
"numForcedPartitions": -1, // when "domainType"=NULL and "useSeqPartition"=false, it returns the default partition numbers for HASH-partitioned table
// -1 indicates the estimated partition number
},
"explain": {...} // execution plan of "sql"
At this phase, the number of join stages are listed at the beginning, which contains:
-
numPipelinedStages
: the number of pipelined joins; -
numSerialStages
: the number of serial joins.
"numPipelinedStages": 2,
"numSerialStages": 3,
pipelinedStages
attribute, which contains the following sub
attributes. "pipelinedStages": {
"rows": 10, // the returned rows
"cost": 35, // the cost for pipelined joins
"numTasks": 10, // the number of pipeline tasks
"most": { // the most time-consuming task
"cost": 20, // the cost for the most time-consuming task
"segments": [ // the partition path for each partitioned table
"20210101/Key1", // returns "" for an in-memory table and "<Empty Segment>" for a partition that has been pruned
""
],
"explain": {
"stage1": {
"left": {...}, // execution plan for retrieving data from left table
"right": {...}, // execution plan for retrieving data from right table
"join": {
"script": "ej(t1, t2, `id, `id)", //script for the join operation
"predicates": [ // predicates pushed down to this join
"t1.id > 3",
"t2.id < 5"
],
"rows": 10, // the returned rows for "stage1"
"cost": 152 // the cost of "stage1"
}
},
"stage2": {
"right": {...}, // execution plan for retrieving data from right table
"join": {...} // same format as the "join" in "stage1"
},
... // other possible stages
}
},
"least": {...}, // the least time-consuming task, same format as the "most" attribute
"resultPartitionInfo": { // info of the returned intermediate table
"isPartitioned": true, // whether the table is partitioned. If true, the following info is returned.
"estimatedRows": 50000, // estimated rows of the table
"domainType": "NULL/VALUE/HASH/...", // partition type. If no such partitions, returns NULL.
"domainScheme": [1, 2, 3], // partitioning scheme for non-NULL "domainType"
"partitionColumns": ["col1", "col2"], // the partitioning columns
"useSeqPartition": false // whether the returned table is SEQ-partitioned
"totalExecutionCost": 20, // the total cost for pipeline tasks
"totalAppendCost": 30, // the cost for appending results to the partitioned tables
// If all joins can be executed in a pipeline, a final query will be executed.
"final": {
"sql": "...",
"rows": 10,
"cost": 237,
"explain": {...} // execution plan for the final query
}
},
serialStages
attribute, which contains
the following sub
attributes:"serialStages": {
"rows": 10, // the returned rows
"cost": 30, // the cost for serial joins
"stage3": { // the stage numbers for serial joins follow sequentially after the pipelined stage
"left": {...}, // execution plan for retrieving data from left table
// returns when the join operation involves only serial joins
"right": {...}, // execution plan for retrieving data from right table
"join": {
"sql": "...",
"rows": 10,
"cost": 237,
"resultPartitionInfo": { // info of the returned intermediate table
"partitionParamsSet": true, // whether the join attempts to return a partitioned table. If true, the following info is returned.
// returns true for all joins other than the last join operation
"forcePartition": true, // whether to forcibly generate a partitioned table
"domainType": "NULL/VALUE/HASH/...", // partition type. If no such partitions, returns NULL.
"domainScheme": [1, 2, 3], // partitioning scheme for non-NULL "domainType"
"partitionColumns": ["col1", "col2"], // the partitioning columns
"useSeqPartition": false // whether the returned table is SEQ-partitioned
"numForcedPartitions": -1, // when "domainType"=NULL and "useSeqPartition"=false, it returns the default partition numbers for HASH-partitioned table
}, // -1 indicates the estimated partition number
"explain": {...} // execution plan for "sql"
}
},
"stage4": {
"right": {...}, // execution plan for retrieving data from right table
"join": {...} // same format as the "join" in "stage3"
},
... // other possible stages
},
where
For the TSDB engine, the required data will be filtered based on the index of
the level file using the where condition. where
may contain
the following sub attributes:
-
TSDBIndexPrefiltering
: Indicates the info of data and where condition during the prefiltering process.-
blocksToBeScanned: The number of blocks to be scanned after the prefiltering.
-
matchedWhereConditions: The number of matched conditions in the WHERE clause that involve the sort keys, including equal and non-equal comparisons (excluding chained comparison), or the use of operators such as IN, BETWEEN, LIKE.
-
-
rows
: The number of rows that satisfy the where condition. -
cost
: The cost of the WHERE clause.
"where": {
"TSDBIndexPrefiltering": {
"blocksToBeScanned": 0,
"matchedWhereConditions": 1
},
"rows": 0,
"cost": 416
}
map
In the map phase, the system identifies all the partitions (on local and remote nodes) involved in the query, then generates the corresponding subqueries and distributes the subqueries to all the partitions involved for parallel execution.
The map
attribute may contain the following sub attributes:
-
generate_reshuffle_query
: This attribute is unique to distributed joins, containing information about the "generate_reshuffle_query" operation. The operation takes place before the distributed join to redistribute data and store them continuously in memory based on the join column(s). If the join column(s) match the partitioning column(s), this operation is not performed. (Note that distributed joins are only available in 2.00 or higher version.) -
partitions
: Information about the partitions involved in this query. The sub attributeslocal
andremote
indicate the number of partitions on the local/remote nodes. -
cost
: The cost of the entire map phase. By the end of this phase, all subqueries are completed. -
detail
: The execution details of subqueries in the map phase:a.
most
: The information of the most time-consuming subquery.b.
least
: The information of the least time-consuming subquery.
"map": {
"generate_reshuffle_query": {
"cost": 2
},
"partitions": {
"local": 10,
"remote": 5,
},
"cost": 100,
"detail": {
"most": {
"sql": "select time,id,value from pt [partition = /iot/1]",
"explain": { ... }
},
"least": {
"sql": "select time,id,value from pt [partition = /iot/4]",
"explain": { ... },
}
}
}
optimize
optimize
indicates how the query is optimized.
-
optimize
: Query optimization information. It contains the following sub attributes: -
field
: Optimized clauses, such as "where", "join" and "group"; or optimization scenarios, such as "single partition query".
"optimize": {
"cost": 3,
"field": ["where", "join", "group"],
"sql": "..." // the optimized query
}
Below are some scenarios where the queries are optimized:
(1) When querying only a single partition, the query's execution plan of the map phase is as follows:
"map": {
"partitions": {
"local": 1, // Or "0"
"remote": 0, // Or "1"
},
"cost": 100,
"optimize": {
"field": "single partition query",
"sql": "...",
"explain": { ... }
}
}
As the query only involves one partition on a single node, merge
is
not required.
(2) context by + csort + limit
In this example, the query uses "context by" in conjunction with "csort" and "limit":
select * from pt where device in ["a", "b"] context by device csort time limit n
Here pt is a partitioned table (with one or multiple partitioning columns). The query will be optimized if the following conditions are satisfied:
-
The "context by" column is filtered by "where". In this example, the device column is filtered by where device in ["a", "b""].
-
The "csort" column (time in this example) is a partitioning column, and the partition type is VALUE or RANGE.
-
"csort" and "context by" can only specify one column.
-
The "context by" column is specified in the "select" clause.
map
returns the following:
"map":{
"optimize":{
"cost":4, // Cost of the optimization
"field":"optimize for CONTEXT BY + CSORT + LIMIT: partial parallel execution."
},
"cost":1082 // Cost of the map phase
}
Based on the query statement and the table partitioning scheme,
field
returns either of the following:
-
optimize for CONTEXT BY + CSORT + LIMIT: partial parallel execution.
-
optimize for CONTEXT BY + CSORT + LIMIT: serial execution.
Note: If the query is optimized during execution, the execution plan may not contain
the attributes merge
and reduce
.
merge
In the merge
phase, the results of the subqueries assigned to each
node are merged.
merge
may contain the following sub attributes:
-
row
: The total number of rows after the merge. -
cost
: The cost of the merge phase. -
detail
: Execution details of the subqueries in the merge phase. a. most: Information about the subquery returning the most rows. b. least: Information about the subquery returning the least rows.
"merge": {
"row": 10000,
"cost": 50,
"detail": {
"most": { // the subquery returning the most rows
"sql": "select time,id,value from pt [partition = /iot/6]",
"explain": { ...}
},
"least": {
"sql": "select time,id,value from pt [partition = /iot/9]",
"explain": { ...}
}
}
}
reduce
The reduce phase combines the results of the subqueries, usually by performing one last query on the merged result. Note that there may or may not be a reduce phase in the execution plan.
reduce
may contain the following sub attributes:
"reduce": {
"sql": "...", // the final query
"explain": { ... }
}
Other attributes
In addition to the attributes described above at each stage of a distributed query, an execution plan may contain other SQL-related attributes.
groupBy
-
sortKey
: Indicates whether the sortColumn is specified in the "group by" condition of the query. If the value is "true", there'll be noalgo
. Note thatsortKey
is only available in 2.00 or higher version.
-
algo
: Indicates the grouping algorithm. It can be "hash", "vectorize" or "sort". When it is "sort", two additional fields are returned:-
inplaceOptimization
: Whether inplace optimization is conducted. Inplace optimization uses the same preset memory to store the results of all groups instead of frequent memory allocation for each temporary result, which reduces overheads and optimizes memory usage. -
optimizedColumns
: If inplaceOptimization returns true, this field returns the columns that were involved in inplace optimization. If the inplaceOptimization returns false, a null value is returned.
-
-
fill
: Indicates the cost to implement the interpolation specified by the interval function.
"groupBy":{
"sortKey":false,
"algo":"hash",
"cost":8
}
// "group by" clause with the interval function
"groupBy": {
"sortKey": false,
"algo": "hash",
"fill": {
"cost": 23
},
"cost": 248
}
// When inplace optimization is applied
"groupBy": {
"sortKey": false,
"algo": "sort",
"inplaceOptimization": true,
"optimizedColumns": [
"std_price0"
],
"cost": 16422
}
contextBy
-
sortKey
: Indicates whether the sortColumn is used in the "context by" clause.
"contextBy":{
"sortKey":false,
"cost":1994
}
Note: sortKey
is only available in the 2.00 version.
join, csort, sort, pivotBy
These attributes only contain the cost
sub attribute.
"join": {
"cost": 10
}
By printing the SQL execution plan and analyzing the time consumed in each part of query execution, it can help us optimize the SQL statement and improve the execution efficiency.
vectorindex
vectorindex
logs information about queries processed with
vector index. It is returned only when querying a DFS table with a vector
index set. vectorindex
may contain the following sub
attributes:
-
useIndex
: whether the query meets the conditions for vector index optimization. If true, the following additional info is provided.
cost
: cost of using vector index.inputRows
: the number of rows to be further searched with vector index.outputRows
: the number of rows returned.
"vectorindex": {
"useIndex": true,
"cost": 457470,
"inputRows": 200000,
"outputRows": 3000
},