diff --git a/src/.vuepress/sidebar/V2.0.x/en-Table.ts b/src/.vuepress/sidebar/V2.0.x/en-Table.ts index 73eee9bcd..5d34832e5 100644 --- a/src/.vuepress/sidebar/V2.0.x/en-Table.ts +++ b/src/.vuepress/sidebar/V2.0.x/en-Table.ts @@ -129,7 +129,7 @@ export const enSidebar = { text: 'System Maintenance', collapsible: true, children: [ - { text: 'Query Performance Analysis', link: 'Query-Performance-Analysis' }, + { text: 'Query Performance Analysis', link: 'Query-Performance-Analysis_apache' }, { text: 'Cluster Maintenance', link: 'Load-Balance_apache' }, { text: 'Maintenance statement', link: 'Maintenance-commands_apache' }, { text: 'Auto-start On Boot', link: 'Auto-Start-On-Boot_apache' }, diff --git a/src/.vuepress/sidebar/V2.0.x/zh-Table.ts b/src/.vuepress/sidebar/V2.0.x/zh-Table.ts index a1f69708b..77e2d51ed 100644 --- a/src/.vuepress/sidebar/V2.0.x/zh-Table.ts +++ b/src/.vuepress/sidebar/V2.0.x/zh-Table.ts @@ -129,7 +129,7 @@ export const zhSidebar = { text: '系统运维', collapsible: true, children: [ - { text: '查询性能分析', link: 'Query-Performance-Analysis' }, + { text: '查询性能分析', link: 'Query-Performance-Analysis_apache' }, { text: '集群维护', link: 'Load-Balance_apache' }, { text: '运维语句', link: 'Maintenance-statement_apache' }, { text: '开机自启', link: 'Auto-Start-On-Boot_apache' }, diff --git a/src/UserGuide/Master/Table/SQL-Manual/SQL-Maintenance-Statements_apache.md b/src/UserGuide/Master/Table/SQL-Manual/SQL-Maintenance-Statements_apache.md index 9909c55e5..89dd859cd 100644 --- a/src/UserGuide/Master/Table/SQL-Manual/SQL-Maintenance-Statements_apache.md +++ b/src/UserGuide/Master/Table/SQL-Manual/SQL-Maintenance-Statements_apache.md @@ -494,7 +494,7 @@ IoTDB> KILL ALL QUERIES; -- teminate all query EXPLAIN ``` -Detailed syntax reference: [EXPLAIN STATEMENT](../User-Manual/Query-Performance-Analysis.md#_1-explain-statement) +Detailed syntax reference: [EXPLAIN STATEMENT](../User-Manual/Query-Performance-Analysis_apache.md#_2-explain) **Example:** @@ -543,7 +543,7 @@ IoTDB> explain select * from t1 EXPLAIN ANALYZE [VERBOSE] ``` -Detailed syntax reference: [EXPLAIN ANALYZE STATEMENT](../User-Manual/Query-Performance-Analysis.md#_2-explain-analyze-statement) +Detailed syntax reference: [EXPLAIN ANALYZE STATEMENT](../User-Manual/Query-Performance-Analysis_apache.md#_3-explain-analyze) **Example:** diff --git a/src/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis.md b/src/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis.md deleted file mode 100644 index 17c0259f0..000000000 --- a/src/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis.md +++ /dev/null @@ -1,392 +0,0 @@ - -# Query Performance Analysis - -Query analysis helps users understand query execution mechanisms and identify performance bottlenecks, facilitating optimization and improving efficiency. This directly enhances user experience and resource utilization. IoTDB provides two query analysis statements: **`EXPLAIN`** and **`EXPLAIN ANALYZE`**. - -- **`EXPLAIN`**: Displays the query execution plan, detailing how IoTDB retrieves and processes data. -- **`EXPLAIN ANALYZE`**: Executes the query and provides detailed performance metrics, such as execution time and resource consumption. Unlike other diagnostic tools, it requires no deployment and focuses on single-query analysis for precise troubleshooting. - -**Performance Analysis Methods Comparison** - -| **Method** | **Installation Difficulty** | **Business Impact** | **Functional Scope** | -| :----------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- | -| **EXPLAIN ANALYZE** | **Low.** No additional components required; built-in SQL statement in IoTDB. | **Low.** Impacts only the analyzed query, with no effect on other workloads. | **Supports cluster systems.** Enables tracing for a single SQL query. | -| **Monitoring Dashboard** | **Medium.** Requires installation of IoTDB monitoring dashboard tool (IoTDB) and enabling monitoring services. | **Medium.** Metrics collection introduces additional overhead. | **Supports cluster systems.** Analyzes overall database query load and latency. | -| **Arthas Sampling** | **Medium.** Requires Java Arthas installation (may be restricted in internal networks; sometimes requires application restart). | **High.** May degrade response speed of online services due to CPU sampling. | **Does n****ot supports cluster systems.** Analyzes overall database query load and latency. | - - - -## 1. EXPLAIN Statement - -### 1.1 Syntax - -The `EXPLAIN` command allows users to view the execution plan of an SQL query. It presents the plan as a series of operators, illustrating how IoTDB processes the query. The syntax is as follows, where `` represents the target query: - -```SQL -EXPLAIN -``` - -### 1.2 Description - -The result of `EXPLAIN` includes information such as data access strategies, whether filtering conditions are pushed down, and the distribution of the query plan across different nodes. This provides users with a means to visualize the internal execution logic of the query. - -```SQL --- Create database -CREATE DATABASE test; - --- Create table -USE test; -CREATE TABLE t1 (device_id STRING ID, type STRING ATTRIBUTE, speed FLOAT); - --- Insert data -INSERT INTO t1(device_id, type, speed) VALUES('car_1', 'Model Y', 120.0); -INSERT INTO t1(device_id, type, speed) VALUES('car_2', 'Model 3', 100.0); - --- Execute EXPLAIN -EXPLAIN SELECT * FROM t1; -``` - -The result shows that IoTDB retrieves data from different data partitions through two `TableScan` nodes and aggregates the data using a `Collect` operator before returning it: - -```SQL -+-----------------------------------------------------------------------------------------------+ -| distribution plan| -+-----------------------------------------------------------------------------------------------+ -| ┌─────────────────────────────────────────────┐ | -| │OutputNode-4 │ | -| │OutputColumns-[time, device_id, type, speed] │ | -| │OutputSymbols: [time, device_id, type, speed]│ | -| └─────────────────────────────────────────────┘ | -| │ | -| │ | -| ┌─────────────────────────────────────────────┐ | -| │Collect-21 │ | -| │OutputSymbols: [time, device_id, type, speed]│ | -| └─────────────────────────────────────────────┘ | -| ┌───────────────────────┴───────────────────────┐ | -| │ │ | -|┌─────────────────────────────────────────────┐ ┌───────────┐ | -|│TableScan-19 │ │Exchange-28│ | -|│QualifiedTableName: test.t1 │ └───────────┘ | -|│OutputSymbols: [time, device_id, type, speed]│ │ | -|│DeviceNumber: 1 │ │ | -|│ScanOrder: ASC │ ┌─────────────────────────────────────────────┐| -|│PushDownOffset: 0 │ │TableScan-20 │| -|│PushDownLimit: 0 │ │QualifiedTableName: test.t1 │| -|│PushDownLimitToEachDevice: false │ │OutputSymbols: [time, device_id, type, speed]│| -|│RegionId: 2 │ │DeviceNumber: 1 │| -|└─────────────────────────────────────────────┘ │ScanOrder: ASC │| -| │PushDownOffset: 0 │| -| │PushDownLimit: 0 │| -| │PushDownLimitToEachDevice: false │| -| │RegionId: 1 │| -| └─────────────────────────────────────────────┘| -+-----------------------------------------------------------------------------------------------+ -``` - -## 2. EXPLAIN ANALYZE Statement - -### 2.1 Syntax - -The `EXPLAIN ANALYZE` statement provides detailed performance metrics by executing the query and analyzing its runtime behavior. The syntax is as follows: - -```SQL -EXPLAIN ANALYZE [VERBOSE] -``` - -- `SELECT_STATEMENT` corresponds to the query statement to be analyzed. -- `VERBOSE` (optional): Prints detailed analysis results. Without this option, some metrics are omitted. - -### 2.2 Description - -`Explain Analyze` is a performance analysis SQL built into the IoTDB query engine. Unlike `Explain`, it runs the query and collects execution metrics, enabling users to trace performance bottlenecks, observe resource usage, and conduct precise performance tuning. - -The output includes detailed statistics such as query planning time, execution time, data partitioning, and resource consumption: - -![](/img/explain-analyze-1.png) - -### 2.3 Detailed Breakdown of EXPLAIN ANALYZE Results - -#### QueryStatistics - -`QueryStatistics` contains high-level statistics about the query execution, including the time spent in each planning stage and the number of query shards. - -- Analyze Cost: Time spent in the SQL analysis phase, including `FetchPartitionCost` and `FetchSchemaCost`. -- Fetch Partition Cost: Time spent fetching partition tables. -- Fetch Schema Cost: Time spent fetching schema and performing permission checks. -- Logical Plan Cost: Time spent building the logical plan. -- Logical Optimization Cost: Time spent optimizing the logical plan. -- Distribution Plan Cost: Time spent building the distributed execution plan. -- Fragment Instance Count: The total number of query shards. Each shard's information is output individually. - -#### FragmentInstance - -A `FragmentInstance` is a wrapper for a query shard in IoTDB. Each query shard outputs its execution information in the result set, including `FragmentStatistics` and operator information. `FragmentStatistics` provides detailed metrics about the shard's execution, including: - -- Total Wall Time: The physical time from the start to the end of the shard's execution. -- Cost of initDataQuerySource: Time spent building the query file list. -- Seq File (unclosed): Number of unclosed (memtable) sequential files. -- Seq File (closed): Number of closed sequential files. -- UnSeq File (unclosed): Number of unclosed (memtable) unsequential files. -- UnSeq File (closed): Number of closed unsequential files. -- Ready Queued Time: Total time the shard's tasks spent in the ready queue (tasks are not blocked but lack query execution thread resources). -- Blocked Queued Time: Total time the shard's tasks spent in the blocked queue (tasks are blocked due to resources like memory or upstream data not being sent). - -Since V2.0.9, the following information will be added to `FragmentInstance`: - -- `OutputPlanNodeId`: Indicates the downstream node that receives data corresponding to the sink node. Only present in sink nodes. -- `sizeInBytes`: Represents the size in bytes of TsBlocks received in exchange nodes (only data size is counted). Only present in exchange nodes. -- Data filtering‑related fields for `tableScan` nodes. Valid only when filter pushdown is applied to tableScan, and only present in tableScan nodes: - - `TimeSeriesIndexFilteredRows`: Number of rows filtered out by TimeseriesMetadata - - `ChunkIndexFilteredRows`: Number of rows filtered out by ChunkMetadata - - `PageIndexFilteredRows`: Number of rows filtered out by PageHeader internal filtering - - `RowScanFilteredRows`: Number of rows filtered out during per-row data inspection. Only displayed when `verbose` is enabled. - - -#### BloomFilter-Related Metrics - -Bloom filters help determine if a sequence exists in a TsFile. They are stored at the end of each TsFile. - -- loadBloomFilterFromCacheCount: Number of times the BloomFilterCache was hit. -- loadBloomFilterFromDiskCount: Number of times BloomFilter was read from disk. -- loadBloomFilterActualIOSize: Disk I/O used when reading BloomFilter (in bytes). -- loadBloomFilterTime: Total time spent reading BloomFilter and checking if a sequence exists (in milliseconds). - -#### TimeSeriesMetadata-Related Metrics - -TimeSeriesMetadata contains indexing information for sequences in a TsFile. Each TsFile has one metadata entry per sequence. - -- loadTimeSeriesMetadataDiskSeqCount: Number of TimeSeriesMetadata entries loaded from closed sequential files. - - Usually equals the number of closed sequential files but may be lower if operators like `LIMIT` are applied. -- loadTimeSeriesMetadataDiskUnSeqCount: Number of TimeSeriesMetadata entries loaded from closed unsequential files. -- loadTimeSeriesMetadataDiskSeqTime: Time spent loading sequential TimeSeriesMetadata from disk. - - Not all loads involve disk I/O, as cache hits may reduce access time. -- loadTimeSeriesMetadataDiskUnSeqTime: Time spent loading unsequential TimeSeriesMetadata from disk. -- loadTimeSeriesMetadataFromCacheCount: Number of cache hits when accessing TimeSeriesMetadata. -- loadTimeSeriesMetadataFromDiskCount: Number of times TimeSeriesMetadata was read from disk. -- loadTimeSeriesMetadataActualIOSize: Disk I/O used for TimeSeriesMetadata reads (in bytes). - -#### Mods File-Related Metrics - -- TimeSeriesMetadataModificationTime: Time spent reading modification (mods) files. - -#### Chunk-Related Metrics - -Chunks are the fundamental units of data storage in TsFiles. - -- constructAlignedChunkReadersDiskCount: Number of Chunks read from closed TsFiles. -- constructAlignedChunkReadersDiskTime: Total time spent reading Chunks from closed TsFiles (including disk IO and decompression). -- pageReadersDecodeAlignedDiskCount: Number of pages decoded from closed TsFiles. -- pageReadersDecodeAlignedDiskTime: Total time spent decoding pages from closed TsFiles. -- loadChunkFromCacheCount: Number of times ChunkCache was hit. - - For aligned devices, each component (including the time column) requests the cache separately. Therefore, `(loadChunkFromCacheCount + loadChunkFromDiskCount)` equals `tsfile number * subSensor number (including the time column) * avg chunk number in each TsFile`. -- loadChunkFromDiskCount: Number of times Chunks were read from disk. -- loadChunkActualIOSize: Disk I/O used when reading Chunks (in bytes). - -### 2.4 Special Notes - -**Query** **Timeout Scenario with EXPLAIN ANALYZE** - -Since `EXPLAIN ANALYZE` runs as a special query type, it cannot return results if execution times out. To aid troubleshooting, IoTDB automatically enables a timed logging mechanism that periodically records partial results to a dedicated log file (`logs/log_explain_analyze.log`). This mechanism requires no user configuration. - -- The logging interval is dynamically calculated based on the query's timeout duration, ensuring at least two log entries before the timeout occurs. -- Users can examine the log file to identify potential causes of the timeout. - -### 2.5 Example - -The following example demonstrates how to use `EXPLAIN ANALYZE`: - -```SQL --- Create Database -CREATE DATABASE test; - --- Create Table -USE test; -CREATE TABLE t1 (device_id STRING ID, type STRING ATTRIBUTE, speed FLOAT); - --- Insert Data -INSERT INTO t1(device_id, type, speed) VALUES('car_1', 'Model Y', 120.0); -INSERT INTO t1(device_id, type, speed) VALUES('car_2', 'Model 3', 100.0); - --- Execute EXPLAIN ANALYZE -EXPLAIN ANALYZE VERBOSE SELECT * FROM t1; -``` - -**Output** **(Simplified):** - -```SQL -+-----------------------------------------------------------------------------------------------+ -| Explain Analyze| -+-----------------------------------------------------------------------------------------------+ -|Analyze Cost: 38.860 ms | -|Fetch Partition Cost: 9.888 ms | -|Fetch Schema Cost: 54.046 ms | -|Logical Plan Cost: 10.102 ms | -|Logical Optimization Cost: 17.396 ms | -|Distribution Plan Cost: 2.508 ms | -|Dispatch Cost: 22.126 ms | -|Fragment Instances Count: 2 | -| | -|FRAGMENT-INSTANCE[Id: 20241127_090849_00009_1.2.0][IP: 0.0.0.0][DataRegion: 2][State: FINISHED]| -| Total Wall Time: 18 ms | -| Cost of initDataQuerySource: 6.153 ms | -| Seq File(unclosed): 1, Seq File(closed): 0 | -| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | -| ready queued time: 0.164 ms, blocked queued time: 0.342 ms | -| Query Statistics: | -| loadBloomFilterFromCacheCount: 0 | -| loadBloomFilterFromDiskCount: 0 | -| loadBloomFilterActualIOSize: 0 | -| loadBloomFilterTime: 0.000 | -| loadTimeSeriesMetadataAlignedMemSeqCount: 1 | -| loadTimeSeriesMetadataAlignedMemSeqTime: 0.246 | -| loadTimeSeriesMetadataFromCacheCount: 0 | -| loadTimeSeriesMetadataFromDiskCount: 0 | -| loadTimeSeriesMetadataActualIOSize: 0 | -| constructAlignedChunkReadersMemCount: 1 | -| constructAlignedChunkReadersMemTime: 0.294 | -| loadChunkFromCacheCount: 0 | -| loadChunkFromDiskCount: 0 | -| loadChunkActualIOSize: 0 | -| pageReadersDecodeAlignedMemCount: 1 | -| pageReadersDecodeAlignedMemTime: 0.047 | -| [PlanNodeId 43]: IdentitySinkNode(IdentitySinkOperator) | -| CPU Time: 5.523 ms | -| output: 2 rows | -| HasNext() Called Count: 6 | -| Next() Called Count: 5 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 31]: CollectNode(CollectOperator) | -| CPU Time: 5.512 ms | -| output: 2 rows | -| HasNext() Called Count: 6 | -| Next() Called Count: 5 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 29]: TableScanNode(TableScanOperator) | -| CPU Time: 5.439 ms | -| output: 1 rows | -| HasNext() Called Count: 3 -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| DeviceNumber: 1 | -| CurrentDeviceIndex: 0 | -| [PlanNodeId 40]: ExchangeNode(ExchangeOperator) | -| CPU Time: 0.053 ms | -| output: 1 rows | -| HasNext() Called Count: 2 | -| Next() Called Count: 1 | -| Estimated Memory Size: : 131072 | -| | -|FRAGMENT-INSTANCE[Id: 20241127_090849_00009_1.3.0][IP: 0.0.0.0][DataRegion: 1][State: FINISHED]| -| Total Wall Time: 13 ms | -| Cost of initDataQuerySource: 5.725 ms | -| Seq File(unclosed): 1, Seq File(closed): 0 | -| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | -| ready queued time: 0.118 ms, blocked queued time: 5.844 ms | -| Query Statistics: | -| loadBloomFilterFromCacheCount: 0 | -| loadBloomFilterFromDiskCount: 0 | -| loadBloomFilterActualIOSize: 0 | -| loadBloomFilterTime: 0.000 | -| loadTimeSeriesMetadataAlignedMemSeqCount: 1 | -| loadTimeSeriesMetadataAlignedMemSeqTime: 0.004 | -| loadTimeSeriesMetadataFromCacheCount: 0 | -| loadTimeSeriesMetadataFromDiskCount: 0 | -| loadTimeSeriesMetadataActualIOSize: 0 | -| constructAlignedChunkReadersMemCount: 1 | -| constructAlignedChunkReadersMemTime: 0.001 | -| loadChunkFromCacheCount: 0 | -| loadChunkFromDiskCount: 0 | -| loadChunkActualIOSize: 0 | -| pageReadersDecodeAlignedMemCount: 1 | -| pageReadersDecodeAlignedMemTime: 0.007 | -| [PlanNodeId 42]: IdentitySinkNode(IdentitySinkOperator) | -| CPU Time: 0.270 ms | -| output: 1 rows | -| HasNext() Called Count: 3 | -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 30]: TableScanNode(TableScanOperator) | -| CPU Time: 0.250 ms | -| output: 1 rows | -| HasNext() Called Count: 3 | -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| DeviceNumber: 1 | -| CurrentDeviceIndex: 0 | -+-----------------------------------------------------------------------------------------------+ -``` - -### 2.6 FAQs - -#### Differences Between WALL TIME and CPU TIME - -- **CPU** **TIME** (also known as processor time or CPU utilization time) refers to the actual time a program spends executing on the CPU, reflecting the amount of CPU resources consumed. -- **WALL TIME** (also known as elapsed time or physical time) refers to the total time from the start to the end of a program's execution, including all waiting periods. - -**Key Scenarios:** - -1. WALL TIME < CPU TIME: This occurs when multiple threads are used for parallel execution. For example, if a query shard runs for 10 seconds in real-world time using two threads, the CPU time would be 20 seconds (10 seconds × 2 threads), while the wall time would remain 10 seconds. -2. WALL TIME > CPU TIME: This typically happens due to resource contention or insufficient query threads: - 1. Blocked by Resource Constraints: If a query shard is blocked (e.g., due to insufficient memory for data transfer or waiting for upstream data), it remains in the Blocked Queue, accumulating wall time without consuming CPU time. - 2. Insufficient Query Threads: If 20 query shards are running concurrently but only 16 query threads are available, 4 shards will be placed in the Ready Queue, waiting for execution. During this waiting period, wall time continues to elapse, but no CPU time is consumed. - -#### Additional Considerations for EXPLAIN ANALYZE - -`EXPLAIN ANALYZE` introduces minimal overhead, as it runs in a separate thread to collect query statistics. These metrics are generated by the system regardless of whether `EXPLAIN ANALYZE` is executed; the command simply retrieves them for user inspection. - -Additionally, `EXPLAIN ANALYZE` iterates through the result set without producing output. Therefore, the reported execution time closely reflects the actual query execution time, with negligible deviation. - -#### Key Metrics for I/O Time - -The following metrics are crucial for evaluating I/O performance during query execution: - -- `loadBloomFilterActualIOSize` -- `loadBloomFilterTime` -- `loadTimeSeriesMetadataAlignedDisk[Seq/Unseq]Time` -- `loadTimeSeriesMetadataActualIOSize` -- `alignedTimeSeriesMetadataModificationTime` -- `constructAlignedChunkReadersDiskTime` -- `loadChunkActualIOSize` - -These metrics were detailed in previous sections. While TimeSeriesMetadata loading is tracked separately for sequential and unsequential files, chunk reading is not currently differentiated. However, the proportion of sequential versus unsequential data can be inferred from TimeSeriesMetadata statistics. - -#### Impact of Unsequential Data on Query Performance - -Unsequential data can negatively affect query performance in the following ways: - -1. **Additional Merge Sort in Memory**: Unsequential data requires an additional merge sort in memory, which is generally a short operation since it involves pure CPU operations. -2. **Overlap in Time Ranges**: Unsequential data can cause time-range overlaps between data chunks, rendering statistical information unusable: - 1. Inability to Skip Chunks: If a query involves value filtering conditions, statistical information cannot be used to skip entire chunks that do not meet the criteria. However, this is less impactful if the query only involves time filtering. - 2. Inability to Compute Aggregates Directly: Statistical information cannot be used to directly compute aggregate values without reading the data. - -Currently, there is no direct method to measure the performance impact of unsequential data. The only approach is to compare query performance before and after merging the unsequential data. However, even after merging, the query still incurs I/O, compression, and decoding overhead, meaning the execution time will not decrease significantly. - -#### Why EXPLAIN ANALYZE Results Are Not Logged on Query Timeout - -If `EXPLAIN ANALYZE` results are missing from the `log_explain_analyze.log` file after a query timeout, the issue may stem from an incomplete system upgrade. Specifically, if the lib package was updated without updating the conf/logback-datanode.xml file, the logging configuration may be outdated. - -**Solution:** - -1. Replace the `conf/logback-datanode.xml` file with the updated version. -2. A system restart is not required, as the new configuration will be hot-loaded automatically. -3. Wait approximately one minute, then re-execute the `EXPLAIN ANALYZE` statement with the `VERBOSE` option to confirm proper logging. \ No newline at end of file diff --git a/src/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis_apache.md b/src/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis_apache.md new file mode 100644 index 000000000..d45af303b --- /dev/null +++ b/src/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis_apache.md @@ -0,0 +1,1829 @@ + +# Query Performance Analysis + +## 1. Overview + +Query analysis helps users understand the execution mechanism and performance bottlenecks of queries, enabling query optimization and performance tuning. The IoTDB table model provides two types of query analysis statements: + +- `EXPLAIN`: Previews the execution plan of a query SQL, showing how IoTDB organizes data retrieval and processing. + +- `EXPLAIN ANALYZE`: Actually executes the query on top of `EXPLAIN`, and displays the time, resource consumption, and operator statistics during query execution. + +The default output of `EXPLAIN` and `EXPLAIN ANALYZE` is designed for human reading and is suitable for direct viewing in the CLI. For scenarios that require stable parsing of query plans or execution statistics, such as Web Console, CI regression testing, and automated performance diagnosis systems, you can use the JSON output format to obtain structured results. The JSON format is supported since V2.0.11. + +### 1.1 Comparison of Query Analysis Methods + +Compared with Arthas sampling and other troubleshooting methods, `EXPLAIN ANALYZE` requires no additional components and can perform distributed tracing on a single SQL, making it more suitable for locating performance issues of specific queries. + +|Method|Installation Difficulty|Business Impact|Functional Scope| +|---|---|---|---| +|`EXPLAIN ANALYZE` statement|Low. No additional components required, built-in IoTDB SQL statement|Low. Only affects the single query being analyzed, no impact on other online workloads|Supports distributed, can trace a single SQL| +|Arthas sampling|Medium. Requires installing the Java Arthas tool|High. CPU sampling may affect online business response speed|Does not support distributed, only supports analysis of overall query load and time consumption of the database| + +## 2. EXPLAIN + +### 2.1 Syntax + +The `EXPLAIN` command is used to view the distributed execution plan of a SQL query. The execution plan is displayed as an operator tree, describing how IoTDB will execute the query. + +```SQL +EXPLAIN [(FORMAT { GRAPHVIZ | JSON })] +``` + +Where: + +|Parameter|Description| +|---|---| +|`SELECT_STATEMENT`|The query statement to be analyzed| +|`FORMAT GRAPHVIZ`|Outputs the distributed plan in the default graphical text form, suitable for human reading| +|`FORMAT JSON`|Outputs the distributed plan as a JSON object, suitable for programmatic parsing (supported since V2.0.11)| + +The format name after `FORMAT` is case-insensitive, for example, `FORMAT json` is equivalent to `FORMAT JSON`. + +### 2.2 Default GRAPHVIZ Format + +The default format of `EXPLAIN` is `GRAPHVIZ`. After execution, you will get the `distribution plan` result column. + +Taking `table1` in the [sample data](../Reference/Sample-Data.md) as an example, the following two statements are equivalent: + +```SQL +EXPLAIN SELECT * FROM table1; +EXPLAIN (FORMAT GRAPHVIZ) SELECT * FROM table1; +``` + +After executing the above statements, the output is as follows: IoTDB reads data from different data partitions through `DeviceTableScanNode` nodes, and aggregates and returns the results through the `Collect` operator. + +```SQL ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| distribution plan| ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | +| │OutputNode-4 │ | +| │OutputColumns-[time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time] │ | +| │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ | +| └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | +| │ | +| │ | +| ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | +| │Collect-33 │ | +| │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ | +| └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | +| ┌───────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────┐ | +| │ │ | +| ┌───────────┐ ┌───────────┐ | +| │Exchange-40│ │Exchange-41│ | +| └───────────┘ └───────────┘ | +| │ │ | +| │ │ | +|┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐| +|│DeviceTableScanNode-32 │ │DeviceTableScanNode-31 │| +|│QualifiedTableName: database1.table1 │ │QualifiedTableName: database1.table1 │| +|│OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│| +|│DeviceNumber: 4 │ │DeviceNumber: 2 │| +|│ScanOrder: ASC │ │ScanOrder: ASC │| +|│PushDownOffset: 0 │ │PushDownOffset: 0 │| +|│PushDownLimit: 0 │ │PushDownLimit: 0 │| +|│PushDownLimitToEachDevice: false │ │PushDownLimitToEachDevice: false │| +|│RegionId: 1 │ │RegionId: 2 │| +|└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘| ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +``` + +### 2.3 JSON Format + +Using `EXPLAIN (FORMAT JSON)` returns the JSON representation of the distributed plan tree. The result set is a single-column output, and in JSON format returns a single row with a single JSON object, making it convenient for JDBC clients or automation tools to read directly. + +The top-level structure of `EXPLAIN (FORMAT JSON)` varies with the query type: + +|Scenario|Top-level Structure|Description| +|---|---|---| +|Regular query|plan node JSON object|The top-level is directly a plan node object such as `OutputNode`| +|Query containing materialized CTE|wrapper JSON object|The top-level contains `cteQueries` and `mainQuery`, used to retain both the CTE subquery plan and the main query plan| + +Each plan node is output as a JSON object. The basic fields are as follows: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`name`|string|The display name formed by concatenating the node type and plan node id, for example `OutputNode-4`| +|`id`|string|plan node id| +|`properties`|object|Node properties. Only output when the node has displayable properties| +|`children`|array|Array of child nodes. Only output when the node has child nodes| + +Common plan node properties include: + +|Node Type|Main Fields/Properties| +|---|---| +|`OutputNode`|`OutputColumns`, `OutputSymbols`| +|`TableScanNode` / `DeviceTableScanNode`|`QualifiedTableName`, `OutputSymbols`, `DeviceNumber`, `ScanOrder`, `TimePredicate`, `PushDownPredicate`, `PushDownOffset`, `PushDownLimit`, `PushDownLimitToEachDevice`, `RegionId`| +|`TreeDeviceViewScanNode`|In addition to table scan properties, adds `TreeDB`, `MeasurementToColumnName`| +|`AggregationNode`|`OutputSymbols`, `Aggregators`, `GroupingKeys`, `Streamable`, `PreGroupedSymbols`, `Step`| +|`FilterNode`|`Predicate`| +|`ProjectNode`|`OutputSymbols`, `Expressions`| +|`LimitNode` / `OffsetNode`|`Count`| +|`SortNode` / `MergeSortNode`|`OrderBy`| +|`JoinNode`|`JoinType`, `Criteria`, `OutputSymbols`| +|`UnionNode`|`OutputSymbols`| +|`ExplainAnalyzeNode`|`ChildPermittedOutputs`| + +When the query contains a materialized CTE, the JSON top-level is no longer a single plan node directly, but is wrapped as: + +```JSON +{ + "cteQueries": [ + { + "name": "cte1", + "plan": { + "name": "OutputNode-", + "id": "" + } + } + ], + "mainQuery": { + "name": "OutputNode-", + "id": "" + } +} +``` + +Where `cteQueries[].plan` and `mainQuery` are both plan node JSON objects. This structure is used to retain both the CTE subquery plan and the main query plan. + +Taking `table1` in the [sample data](../Reference/Sample-Data.md) as an example: + +```SQL +EXPLAIN (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "CollectNode-33", + "id": "33", + "children": [ + { + "name": "ExchangeNode-40", + "id": "40", + "children": [ + { + "name": "DeviceTableScanNode-32", + "id": "32", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-41", + "id": "41", + "children": [ + { + "name": "DeviceTableScanNode-31", + "id": "31", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] +} ++-----------------+ +``` + +## 3. EXPLAIN ANALYZE + +### 3.1 Syntax + +`EXPLAIN ANALYZE` is a performance analysis SQL built into the IoTDB query engine. Unlike `EXPLAIN`, it actually executes the corresponding query plan and collects execution information, which can be used to track the specific performance distribution of a query, assisting in resource observation, performance tuning, and exception analysis. + +```SQL +EXPLAIN ANALYZE [VERBOSE] [(FORMAT { TEXT | JSON })] +``` + +Where: + +|Parameter|Description| +|---|---| +|`SELECT_STATEMENT`|The query statement to be analyzed| +|`VERBOSE`|Prints more detailed analysis results. When not specified, some fine-grained statistics are omitted| +|`FORMAT TEXT`|Outputs analysis results in the default text form, suitable for human reading| +|`FORMAT JSON`|Outputs analysis results as a JSON object, suitable for programmatic parsing (supported since V2.0.11)| + +### 3.2 Default TEXT Format + +The default format of `EXPLAIN ANALYZE` is `TEXT`. After execution, you will get the `Explain Analyze` result column. The results consist of query planning statistics, fragment instance statistics, data reading statistics, and operator tree statistics. + +- **Query Planning Statistics** + +`QueryStatistics` contains query-level statistics: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`Analyze Cost`|Query planning statistics|Time consumed in the SQL analysis phase| +|`Fetch Partition Cost`|Query planning statistics|Time consumed fetching the partition table| +|`Fetch Schema Cost`|Query planning statistics|Time consumed fetching metadata and permission validation| +|`Logical Plan Cost`|Query planning statistics|Time consumed building the logical plan| +|`Logical Optimization Cost`|Query planning statistics|Time consumed in logical plan optimization| +|`Distribution Plan Cost`|Query planning statistics|Time consumed building the distributed plan| +|`Dispatch Cost`|Query planning statistics|Time consumed dispatching fragment instances| +|`Fragment Instances Count`|Query planning statistics|Total number of query fragments; each fragment's information is output in sequence| + +- **Fragment Instance Statistics** + +`FragmentInstance` is the encapsulation of an IoTDB query fragment. Each query fragment outputs a copy of execution information, mainly including fragment statistics and operator information. + +Fragment statistics include: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`Total Wall Time`|Fragment statistics|Physical time from when the fragment starts execution to when it finishes| +|`Cost of initDataQuerySource`|Fragment statistics|Time consumed building the query file list| +|`Seq File(unclosed)`|Fragment statistics|Number of unclosed sequential files| +|`Seq File(closed)`|Fragment statistics|Number of closed sequential files| +|`UnSeq File(unclosed)`|Fragment statistics|Number of unclosed out-of-order files| +|`UnSeq File(closed)`|Fragment statistics|Number of closed out-of-order files| +|`ready queued time`|Fragment statistics|Total time all tasks of the query fragment spent in the ready queue| +|`blocked queued time`|Fragment statistics|Total time all tasks of the query fragment spent in the blocked queue| + +- **Data Reading Statistics** + +`Query Statistics` detail fields include: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`loadBloomFilterFromCacheCount`|BloomFilter|Number of BloomFilterCache hits| +|`loadBloomFilterFromDiskCount`|BloomFilter|Number of times BloomFilter was read from disk| +|`loadBloomFilterActualIOSize`|BloomFilter|Disk IO generated when reading BloomFilter from disk, in bytes| +|`loadBloomFilterTime`|BloomFilter|Total time spent reading BloomFilter and computing whether a series exists, in ms| +|`loadTimeSeriesMetadataDiskSeqCount`|TimeSeriesMetadata|Number of TimeSeriesMetadata loaded from closed sequential files| +|`loadTimeSeriesMetadataDiskUnSeqCount`|TimeSeriesMetadata|Number of TimeSeriesMetadata loaded from closed out-of-order files| +|`loadTimeSeriesMetadataDiskSeqTime`|TimeSeriesMetadata|Time consumed loading TimeSeriesMetadata from closed sequential files| +|`loadTimeSeriesMetadataDiskUnSeqTime`|TimeSeriesMetadata|Time consumed loading TimeSeriesMetadata from closed out-of-order files| +|`loadTimeSeriesMetadataFromCacheCount`|TimeSeriesMetadata|Number of TimeSeriesMetadataCache hits| +|`loadTimeSeriesMetadataFromDiskCount`|TimeSeriesMetadata|Number of times TimeSeriesMetadata was read from disk| +|`loadTimeSeriesMetadataActualIOSize`|TimeSeriesMetadata|Disk IO generated when reading TimeSeriesMetadata from disk, in bytes| +|`TimeSeriesMetadataModificationTime`|Mods file|Time consumed reading mods files| +|`constructAlignedChunkReadersDiskCount`|Chunk|Number of times ChunkReader was constructed| +|`constructAlignedChunkReadersDiskTime`|Chunk|Total time consumed constructing ChunkReader, including disk IO and decompression| +|`pageReadersDecodeAlignedDiskCount`|Chunk|Number of pages decoded| +|`pageReadersDecodeAlignedDiskTime`|Chunk|Total time consumed decoding pages| +|`loadChunkFromCacheCount`|Chunk|Number of ChunkCache hits| +|`loadChunkFromDiskCount`|Chunk|Number of times Chunk was read from disk| +|`loadChunkActualIOSize`|Chunk|Disk IO generated when reading Chunk from disk, in bytes| + +- **Operator Tree Statistics** + +Operator tree statistics include: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`CPU Time`|Operator tree statistics|CPU execution time of the current operator| +|`output`|Operator tree statistics|Number of output rows of the current operator| +|`HasNext() Called Count`|Operator tree statistics|Number of times the current operator's `HasNext()` interface was called| +|`Next() Called Count`|Operator tree statistics|Number of times the current operator's `Next()` interface was called| +|`Estimated Memory Size`|Operator tree statistics|Estimated memory usage of the current operator| +|Node-specific statistics|Operator tree statistics|Custom statistics fields output by different operators, such as `DeviceNumber`, `CurrentDeviceIndex`, `OutputPlanNodeId`, `size_in_bytes`, etc.| + +Common node-specific statistics fields include: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`DeviceNumber`|`TableScan` related nodes|Number of devices involved in the current table scan| +|`CurrentDeviceIndex`|`TableScan` related nodes|Index of the device currently being scanned| +|`OutputPlanNodeId`|sink node|The downstream data-receiving node corresponding to the sink node| +|`size_in_bytes`|exchange node|Number of bytes of TsBlock received by the exchange node, only counting data size| +|`TimeSeriesIndexFilteredRows`|table scan node when filter is pushed down to table scan|Number of data rows filtered out by series metadata| +|`ChunkIndexFilteredRows`|table scan node when filter is pushed down to table scan|Number of data rows filtered out by chunk metadata| +|`PageIndexFilteredRows`|table scan node when filter is pushed down to table scan|Number of data rows filtered out by page header internal statistics| +|`RowScanFilteredRows`|table scan node when filter is pushed down to table scan and `VERBOSE` is used|Number of rows filtered out during row-by-row data checking| + +The actual output fields vary with the query type, the operators involved, whether `VERBOSE` is used, and whether filter pushdown is triggered. When the corresponding logic is not triggered, the relevant fields may not appear in the result. + +- **Text Output Example** + +Taking `table1` in the [sample data](../Reference/Sample-Data.md) as an example, the following two statements are equivalent: + +```SQL +EXPLAIN ANALYZE SELECT * FROM table1; +EXPLAIN ANALYZE (FORMAT TEXT) SELECT * FROM table1; +``` + +```SQL ++-------------------------------------------------------------------------------------------------------------------------+ +| Explain Analyze| ++-------------------------------------------------------------------------------------------------------------------------+ +|Analyze Cost: 3.862 ms | +|Fetch Partition Cost: 0.785 ms | +|Fetch Schema Cost: 5.473 ms | +|Logical Plan Cost: 37.350 ms | +|Logical Optimization Cost: 4.340 ms | +|Distribution Plan Cost: 1.287 ms | +|Dispatch Cost: 8.711 ms | +|Fragment Instances Count: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.2.0][IP: 127.0.0.1:10730][DataRegion: virtual_data_region][State: FINISHED]| +| Total Wall Time: 64 ms | +| Cost of initDataQuerySource: 0.000 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.428 ms, blocked queued time: 11.410 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 69]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 28.691 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| DownStreamPlanNodeId: 66 | +| [PlanNodeId 55]: CollectNode(CollectOperator) | +| CPU Time: 28.656 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| [PlanNodeId 64]: ExchangeNode(ExchangeOperator) | +| CPU Time: 13.310 ms | +| output: 12 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 3264 | +| [PlanNodeId 65]: ExchangeNode(ExchangeOperator) | +| CPU Time: 15.286 ms | +| output: 6 rows | +| HasNext() Called Count: 3 | +| Next() Called Count: 2 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 1632 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.3.0][IP: 127.0.0.1:10730][DataRegion: 1][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 7.652 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 2, UnSeq File(closed): 0 | +| ready queued time: 0.089 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 67]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 2.007 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 64 | +| [PlanNodeId 54]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 1.981 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 4 | +| CurrentDeviceIndex: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.4.0][IP: 127.0.0.1:10730][DataRegion: 2][State: FINISHED] | +| Total Wall Time: 48 ms | +| Cost of initDataQuerySource: 0.933 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 1, UnSeq File(closed): 0 | +| ready queued time: 0.141 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 68]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 1.846 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 65 | +| [PlanNodeId 53]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 1.755 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 2 | +| CurrentDeviceIndex: 1 | ++-------------------------------------------------------------------------------------------------------------------------+ +``` + +- **VERBOSE Output Example** + +Using `VERBOSE` you can view finer-grained data reading and filtering statistics: + +```SQL +EXPLAIN ANALYZE VERBOSE SELECT * FROM table1; +EXPLAIN ANALYZE VERBOSE (FORMAT TEXT) SELECT * FROM table1; +``` + +```YAML ++-------------------------------------------------------------------------------------------------------------------------+ +| Explain Analyze| ++-------------------------------------------------------------------------------------------------------------------------+ +|Analyze Cost: 1.016 ms | +|Fetch Partition Cost: 0.555 ms | +|Fetch Schema Cost: 3.789 ms | +|Logical Plan Cost: 0.491 ms | +|Logical Optimization Cost: 2.625 ms | +|Distribution Plan Cost: 0.393 ms | +|Dispatch Cost: 37.041 ms | +|Fragment Instances Count: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.2.0][IP: 127.0.0.1:10730][DataRegion: virtual_data_region][State: FINISHED]| +| Total Wall Time: 43 ms | +| Cost of initDataQuerySource: 0.000 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.248 ms, blocked queued time: 23.757 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 0 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.000 | +| loadTimeSeriesMetadataFromCacheCount: 0 | +| loadTimeSeriesMetadataFromDiskCount: 0 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| loadChunkFromCacheCount: 0 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 69]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 0.988 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| DownStreamPlanNodeId: 66 | +| [PlanNodeId 55]: CollectNode(CollectOperator) | +| CPU Time: 0.954 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| [PlanNodeId 64]: ExchangeNode(ExchangeOperator) | +| CPU Time: 0.569 ms | +| output: 12 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 3264 | +| [PlanNodeId 65]: ExchangeNode(ExchangeOperator) | +| CPU Time: 0.354 ms | +| output: 6 rows | +| HasNext() Called Count: 3 | +| Next() Called Count: 2 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 1632 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.3.0][IP: 127.0.0.1:10730][DataRegion: 1][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 0.224 ms | +| Seq File(unclosed): 0, Seq File(closed): 2 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.025 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 2 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.018 | +| loadTimeSeriesMetadataAlignedDiskSeqCount: 4 | +| loadTimeSeriesMetadataAlignedDiskSeqTime: 0.189 | +| loadTimeSeriesMetadataFromCacheCount: 18 | +| loadTimeSeriesMetadataFromDiskCount: 2 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| alignedTimeSeriesMetadataModificationTime: 0.026 | +| constructAlignedChunkReadersDiskCount: 4 | +| constructAlignedChunkReadersDiskTime: 3.251 | +| loadChunkFromCacheCount: 18 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| pageReadersDecodeAlignedDiskCount: 4 | +| pageReadersDecodeAlignedDiskTime: 0.210 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 67]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 5.150 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 64 | +| [PlanNodeId 54]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 5.136 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 4 | +| CurrentDeviceIndex: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.4.0][IP: 127.0.0.1:10730][DataRegion: 2][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 0.397 ms | +| Seq File(unclosed): 0, Seq File(closed): 1 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.075 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 0 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.000 | +| loadTimeSeriesMetadataAlignedDiskSeqCount: 2 | +| loadTimeSeriesMetadataAlignedDiskSeqTime: 0.313 | +| loadTimeSeriesMetadataFromCacheCount: 10 | +| loadTimeSeriesMetadataFromDiskCount: 0 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| alignedTimeSeriesMetadataModificationTime: 0.046 | +| constructAlignedChunkReadersDiskCount: 2 | +| constructAlignedChunkReadersDiskTime: 2.307 | +| loadChunkFromCacheCount: 10 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| pageReadersDecodeAlignedDiskCount: 2 | +| pageReadersDecodeAlignedDiskTime: 0.129 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 68]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 4.187 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 65 | +| [PlanNodeId 53]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 4.172 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 2 | +| CurrentDeviceIndex: 1 | ++-------------------------------------------------------------------------------------------------------------------------+ +``` + +### 3.3 JSON Format + +Using `EXPLAIN ANALYZE (FORMAT JSON)` returns structured information such as plan phase time consumption, fragment instance statistics, and operator statistics. The JSON format is supported since V2.0.11. + +#### 3.3.1 Default JSON Output + +```SQL +EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM t1; +``` + +The top-level JSON structure is as follows: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`planStatistics`|object|Time consumption of query analysis, partition fetching, schema fetching, logical planning, logical optimization, distributed planning, dispatch, and other phases| +|`fragmentInstancesCount`|number|Number of fragment instances for which statistics are returned| +|`fragmentInstances`|array|Execution statistics of each fragment instance| + +The main fields of `planStatistics` are as follows: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`analyzeCostMs`|planStatistics|SQL analysis time, in ms| +|`fetchPartitionCostMs`|planStatistics|Time to fetch partition information, in ms| +|`fetchSchemaCostMs`|planStatistics|Time to fetch schema information, in ms| +|`logicalPlanCostMs`|planStatistics|Logical plan generation time, in ms| +|`logicalOptimizationCostMs`|planStatistics|Logical optimization time, in ms| +|`distributionPlanCostMs`|planStatistics|Distributed plan generation time, in ms| +|`dispatchCostMs`|planStatistics|Fragment dispatch time, in ms| + +The main fields of `fragmentInstances[]` are as follows: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`id`|fragmentInstances[]|fragment instance id| +|`ip`|fragmentInstances[]|IP of the node that executed this fragment instance| +|`dataRegion`|fragmentInstances[]|Corresponding DataRegion| +|`state`|fragmentInstances[]|Final state of the fragment instance| +|`totalWallTimeMs`|fragmentInstances[]|Total wall time, in ms| +|`initDataQuerySourceCostMs`|fragmentInstances[]|Data source initialization time, in ms| +|`initDataQuerySourceRetryCount`|fragmentInstances[]|Number of data source initialization retries. Only output when greater than 0| +|`seqFileUnclosed`, `seqFileClosed`, `unseqFileUnclosed`, `unseqFileClosed`|fragmentInstances[]|Number of sequential and out-of-order files involved| +|`readyQueuedTimeMs`, `blockQueuedTimeMs`|fragmentInstances[]|Ready / block queue waiting time, in ms| +|`queryStatistics`|fragmentInstances[]|Query statistics such as scanning, filtering, chunk/page reader| +|`operators`|fragmentInstances[]|Operator tree and execution statistics of each operator| + +`operators` is a tree structure corresponding to the plan tree. The main fields are as follows: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`planNodeId`|operators|Corresponding plan node id| +|`nodeType`|operators|plan node type| +|`operatorType`|operators|Actual executed operator type| +|`count`|operators|Operator statistics aggregation count. Only output when present| +|`cpuTimeMs`|operators|Operator CPU execution time, in ms| +|`outputRows`|operators|Number of output rows| +|`hasNextCalledCount`, `nextCalledCount`|operators|Number of operator iteration calls| +|`estimatedMemorySize`|operators|Estimated memory usage. Only output when non-zero| +|`specifiedInfo`|operators|Operator-specific statistics. Only output when non-empty| +|`children`|operators|Child operator array| + +Taking `table1` in the [sample data](../Reference/Sample-Data.md) as an example: + +```SQL +EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 4.948, + "fetchPartitionCostMs": 2.104, + "fetchSchemaCostMs": 13.321, + "logicalPlanCostMs": 2.264, + "logicalOptimizationCostMs": 7.941, + "distributionPlanCostMs": 1.434, + "dispatchCostMs": 9.023 + }, + "fragmentInstancesCount": 3, + "fragmentInstances": [ + { + "id": "20260722_064302_00101_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "virtual_data_region", + "state": "FINISHED", + "totalWallTimeMs": 97, + "initDataQuerySourceCostMs": 0.0, + "seqFileUnclosed": 0, + "seqFileClosed": 0, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.858, + "blockQueuedTimeMs": 71.752, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "69", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 6.082, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "DownStreamPlanNodeId": "66" + }, + "children": [ + { + "planNodeId": "55", + "nodeType": "CollectNode", + "operatorType": "CollectOperator", + "cpuTimeMs": 6.065, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "children": [ + { + "planNodeId": "64", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 1.119, + "outputRows": 12, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "3264" + } + }, + { + "planNodeId": "65", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 4.909, + "outputRows": 6, + "hasNextCalledCount": 3, + "nextCalledCount": 2, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "1632" + } + } + ] + } + ] + } + }, + { + "id": "20260722_064302_00101_1.3.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 63, + "initDataQuerySourceCostMs": 0.338, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.273, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "67", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 57.338, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "64" + }, + "children": [ + { + "planNodeId": "54", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 57.248, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "4", + "CurrentDeviceIndex": "3" + } + } + ] + } + }, + { + "id": "20260722_064302_00101_1.4.0", + "ip": "127.0.0.1:10730", + "dataRegion": "2", + "state": "FINISHED", + "totalWallTimeMs": 79, + "initDataQuerySourceCostMs": 0.231, + "seqFileUnclosed": 0, + "seqFileClosed": 1, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.038, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "68", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 66.387, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "65" + }, + "children": [ + { + "planNodeId": "53", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 66.362, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "2", + "CurrentDeviceIndex": "1" + } + } + ] + } + } + ] +} ++---------------+ +``` + +#### 3.3.2 VERBOSE JSON Output + +When using `EXPLAIN ANALYZE VERBOSE (FORMAT JSON)`, the top-level structure is the same as the non-verbose JSON, but `queryStatistics` supplements finer-grained statistics beyond the basic filtered row counts, including bloom filter, time series metadata, chunk, page reader, modification, and actual IO size information. + +The main differences between `VERBOSE` and non-`VERBOSE` JSON output are as follows: + +|Output Mode|Top-level Structure|`queryStatistics` Field Granularity|Typical Fields| +|---|---|---|---| +|`EXPLAIN ANALYZE (FORMAT JSON)`|`planStatistics`, `fragmentInstancesCount`, `fragmentInstances`|Default statistics fields|`timeSeriesIndexFilteredRows`, `chunkIndexFilteredRows`, `pageIndexFilteredRows`| +|`EXPLAIN ANALYZE VERBOSE (FORMAT JSON)`|Same as non-`VERBOSE`|More detailed scanning, reading, and filtering statistics|`loadBloomFilterFromCacheCount`, `loadTimeSeriesMetadataFromCacheCount`, `loadChunkFromDiskCount`, `pageReadersDecodeAlignedMemCount`, `rowScanFilteredRows`| + +Taking `table1` in the [sample data](../Reference/Sample-Data.md) as an example: + +```SQL +EXPLAIN ANALYZE VERBOSE (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 3.505, + "fetchPartitionCostMs": 2.003, + "fetchSchemaCostMs": 9.198, + "logicalPlanCostMs": 1.481, + "logicalOptimizationCostMs": 6.684, + "distributionPlanCostMs": 0.711, + "dispatchCostMs": 8.32 + }, + "fragmentInstancesCount": 3, + "fragmentInstances": [ + { + "id": "20260722_064502_00103_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "virtual_data_region", + "state": "FINISHED", + "totalWallTimeMs": 48, + "initDataQuerySourceCostMs": 0.0, + "seqFileUnclosed": 0, + "seqFileClosed": 0, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.209, + "blockQueuedTimeMs": 17.997, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 0, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.0, + "loadTimeSeriesMetadataFromCacheCount": 0, + "loadTimeSeriesMetadataFromDiskCount": 0, + "loadTimeSeriesMetadataActualIOSize": 0, + "loadChunkFromCacheCount": 0, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "69", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 26.368, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "DownStreamPlanNodeId": "66" + }, + "children": [ + { + "planNodeId": "55", + "nodeType": "CollectNode", + "operatorType": "CollectOperator", + "cpuTimeMs": 26.32, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "children": [ + { + "planNodeId": "64", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 0.47, + "outputRows": 12, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "3264" + } + }, + { + "planNodeId": "65", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 25.802, + "outputRows": 6, + "hasNextCalledCount": 3, + "nextCalledCount": 2, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "1632" + } + } + ] + } + ] + } + }, + { + "id": "20260722_064502_00103_1.3.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 21, + "initDataQuerySourceCostMs": 0.232, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.007, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 2, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.02, + "loadTimeSeriesMetadataAlignedDiskSeqCount": 4, + "loadTimeSeriesMetadataAlignedDiskSeqTimeMs": 0.483, + "loadTimeSeriesMetadataFromCacheCount": 18, + "loadTimeSeriesMetadataFromDiskCount": 2, + "loadTimeSeriesMetadataActualIOSize": 0, + "alignedTimeSeriesMetadataModificationTimeMs": 0.061, + "constructAlignedChunkReadersDiskCount": 4, + "constructAlignedChunkReadersDiskTimeMs": 2.693, + "loadChunkFromCacheCount": 18, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "pageReadersDecodeAlignedDiskCount": 4, + "pageReadersDecodeAlignedDiskTimeMs": 0.22, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "67", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 5.002, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "64" + }, + "children": [ + { + "planNodeId": "54", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 4.94, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "4", + "CurrentDeviceIndex": "3" + } + } + ] + } + }, + { + "id": "20260722_064502_00103_1.4.0", + "ip": "127.0.0.1:10730", + "dataRegion": "2", + "state": "FINISHED", + "totalWallTimeMs": 32, + "initDataQuerySourceCostMs": 0.253, + "seqFileUnclosed": 0, + "seqFileClosed": 1, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.027, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 0, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.0, + "loadTimeSeriesMetadataAlignedDiskSeqCount": 2, + "loadTimeSeriesMetadataAlignedDiskSeqTimeMs": 0.188, + "loadTimeSeriesMetadataFromCacheCount": 10, + "loadTimeSeriesMetadataFromDiskCount": 0, + "loadTimeSeriesMetadataActualIOSize": 0, + "alignedTimeSeriesMetadataModificationTimeMs": 0.037, + "constructAlignedChunkReadersDiskCount": 2, + "constructAlignedChunkReadersDiskTimeMs": 1.807, + "loadChunkFromCacheCount": 10, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "pageReadersDecodeAlignedDiskCount": 2, + "pageReadersDecodeAlignedDiskTimeMs": 0.62, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "68", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 3.652, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "65" + }, + "children": [ + { + "planNodeId": "53", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 3.633, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "2", + "CurrentDeviceIndex": "1" + } + } + ] + } + } + ] +} +``` + +## 4. Scenario Examples + +### 4.1 CTE Query Plan + +When `EXPLAIN (FORMAT JSON)` analyzes a query containing a materialized CTE, the output uses the wrapped structure of `cteQueries` + `mainQuery`. + +Taking `table1` and `table2` in the [sample data](../Reference/Sample-Data.md) as an example: + +```SQL +EXPLAIN (FORMAT JSON) +WITH cte1 AS MATERIALIZED (SELECT * FROM table2) +SELECT * FROM table1 +WHERE table1.device_id IN (SELECT device_id FROM cte1); +``` + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "cteQueries": [ + { + "name": "cte1", + "plan": { + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "CollectNode-33", + "id": "33", + "children": [ + { + "name": "ExchangeNode-40", + "id": "40", + "children": [ + { + "name": "DeviceTableScanNode-32", + "id": "32", + "properties": { + "QualifiedTableName": "database1.table2", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-41", + "id": "41", + "children": [ + { + "name": "DeviceTableScanNode-31", + "id": "31", + "properties": { + "QualifiedTableName": "database1.table2", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] + } + } + ], + "mainQuery": { + "name": "OutputNode-12", + "id": "12", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "ProjectNode-35", + "id": "35", + "properties": { + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "Expressions": [ + "\"time\"", + "\"region\"", + "\"plant_id\"", + "\"device_id\"", + "\"model_id\"", + "\"maintenance\"", + "\"temperature\"", + "\"humidity\"", + "\"status\"", + "\"arrival_time\"" + ] + }, + "children": [ + { + "name": "FilterNode-138", + "id": "138", + "properties": { + "Predicate": "\"expr\"" + }, + "children": [ + { + "name": "SemiJoinNode-109", + "id": "109", + "children": [ + { + "name": "ExchangeNode-218", + "id": "218", + "children": [ + { + "name": "MergeSortNode-197", + "id": "197", + "properties": { + "OrderBy": "{orderBy\u003d[device_id], orderings\u003d{device_id\u003dASC NULLS LAST}}" + }, + "children": [ + { + "name": "ExchangeNode-216", + "id": "216", + "children": [ + { + "name": "DeviceTableScanNode-196", + "id": "196", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "true", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-217", + "id": "217", + "children": [ + { + "name": "DeviceTableScanNode-195", + "id": "195", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "true", + "RegionId": "2" + } + } + ] + } + ] + } + ] + }, + { + "name": "ExchangeNode-219", + "id": "219", + "children": [ + { + "name": "SortNode-137", + "id": "137", + "properties": { + "OrderBy": "{orderBy\u003d[device_id_3], orderings\u003d{device_id_3\u003dASC NULLS FIRST}}" + }, + "children": [ + { + "name": "ProjectNode-6", + "id": "6", + "properties": { + "OutputSymbols": [ + "device_id_3" + ], + "Expressions": [ + "\"device_id_3\"" + ] + }, + "children": [ + { + "name": "CteScanNode-2", + "id": "2" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } +} ++-----------------+ +``` + +### 4.2 Prepared Statement and Dynamic SQL + +When analyzing prepared statements and dynamic SQL via `EXECUTE` or `EXECUTE IMMEDIATE`, the specified output format continues to take effect after the statement is expanded, and will not fall back to the default `GRAPHVIZ` or `TEXT` format. + +```SQL +PREPARE explain_json_stmt FROM SELECT * FROM table1 WHERE device_id = ?; +EXPLAIN (FORMAT JSON) EXECUTE explain_json_stmt USING '101'; + +EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE 'SELECT * FROM table1 WHERE device_id = ?' USING '101'; +``` + +Where: + +- `EXPLAIN (FORMAT JSON) EXECUTE ...` returns a plan node JSON object. + +- `EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE ...` returns a JSON object containing `planStatistics`, `fragmentInstancesCount`, and `fragmentInstances`. + +1. `EXPLAIN (FORMAT JSON) EXECUTE ...` output structure example: + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-5", + "id": "5", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "DeviceTableScanNode-46", + "id": "46", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] +} ++-----------------+ +``` + +2. `EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE ...` output structure example: + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 2.904, + "fetchPartitionCostMs": 1.35, + "fetchSchemaCostMs": 5.259, + "logicalPlanCostMs": 1.123, + "logicalOptimizationCostMs": 6.157, + "distributionPlanCostMs": 0.571, + "dispatchCostMs": 5.909 + }, + "fragmentInstancesCount": 1, + "fragmentInstances": [ + { + "id": "20260722_065558_00118_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 10, + "initDataQuerySourceCostMs": 1.768, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.059, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "74", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 9.193, + "outputRows": 10, + "hasNextCalledCount": 7, + "nextCalledCount": 6, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "73" + }, + "children": [ + { + "planNodeId": "68", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 9.055, + "outputRows": 10, + "hasNextCalledCount": 7, + "nextCalledCount": 6, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "3", + "CurrentDeviceIndex": "2" + } + } + ] + } + } + ] +} ++---------------+ +``` + +### 4.3 JSON Display in CLI + +The CLI outputs regular query results in tabular format by default. For JSON-format query analysis results, if the JSON body continues to be placed in table cells, each row would carry `|` borders, which is not conducive to copying, saving, and JSON parsing. + +The CLI switches to raw JSON display when the following conditions are met: + +|Condition|Description| +|---|---| +|The result set has only one column|Avoids affecting normal multi-column query display| +|The column name is `distribution plan` or `Explain Analyze`|Only matches the result columns of `EXPLAIN` / `EXPLAIN ANALYZE`| +|The first result value trimmed starts with `{` or `[`|Only enables raw output for JSON object / array content| + +Raw JSON display rules: + +- The CLI still outputs the column name header and outer separator line, so users can identify the current column as `distribution plan` or `Explain Analyze`. + +- The JSON body is output line by line as-is, without adding `|` table borders. + +- After the JSON ends, a separator line and row count statistics are output. + +- Non-JSON `EXPLAIN`, default text `EXPLAIN ANALYZE`, and regular query results still use the original table output. + +The differences between raw JSON output and regular table output are as follows: + +|Output Type|Applicable Result|Display Method|Copy and Parse| +|---|---|---|---| +|Regular table output|Regular query results, default `EXPLAIN`, default `EXPLAIN ANALYZE`|Each row's content is placed inside table borders|Suitable for human viewing, not suitable for direct JSON copy parsing| +|Raw JSON output|`EXPLAIN (FORMAT JSON)`, `EXPLAIN ANALYZE (FORMAT JSON)`|Retains column name header, JSON body has no table borders|Suitable for direct copying, saving, or handing to a JSON parser| + +Example: + +```SQL +start-cli.sh -sql_dialect table -e "EXPLAIN (FORMAT JSON) SELECT time, device_id, temperature FROM database1.table1" +``` + +```SQL ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "device_id", + "temperature" + ], + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ] + }, + "children": [ + { + "name": "CollectNode-42", + "id": "42", + "children": [ + { + "name": "ExchangeNode-49", + "id": "49", + "children": [ + { + "name": "DeviceTableScanNode-41", + "id": "41", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-50", + "id": "50", + "children": [ + { + "name": "DeviceTableScanNode-40", + "id": "40", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] +} ++-----------------+ +``` + +### 4.4 Illegal Format Combinations + +`EXPLAIN` and `EXPLAIN ANALYZE` support different formats. Illegal format combinations return a clear error. + +|Statement|Default Format|Supported Formats|Unsupported Formats| +|---|---|---|---| +|`EXPLAIN`|`GRAPHVIZ`|`GRAPHVIZ`, `JSON`|`TEXT`, `XML`, other unknown formats| +|`EXPLAIN ANALYZE`|`TEXT`|`TEXT`, `JSON`|`GRAPHVIZ`, `XML`, other unknown formats| + +Example: + +```SQL +EXPLAIN (FORMAT TEXT) SELECT * FROM table1; +EXPLAIN ANALYZE (FORMAT GRAPHVIZ) SELECT * FROM table1; +EXPLAIN (FORMAT XML) SELECT * FROM table1; +``` + +Corresponding errors: + +```SQL +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN format: TEXT. Supported formats: GRAPHVIZ, JSON +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN ANALYZE format: GRAPHVIZ. Supported formats: TEXT, JSON +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN format: XML. Supported formats: GRAPHVIZ, JSON +``` + +## 5. FAQ + +### 5.1 What to do when a query times out? + +`EXPLAIN ANALYZE` actually executes the query, so it may also fail to return complete results when the query times out. To assist troubleshooting, IoTDB writes the current analysis results to a dedicated log in text form at certain time intervals; the log interval is calculated based on the query timeout, ensuring at least two result records before timeout. + +When encountering a timeout, handle it in the following order: + +1. Check the staged analysis results for the corresponding query in `logs/log_explain_analyze.log`. + +2. If there are no results in the log, check whether only the lib package was replaced during the upgrade, and `conf/logback-datanode.xml` was not replaced synchronously. + +3. After replacing the configuration, no restart is required. Wait for the configuration hot reload to complete, then execute `EXPLAIN ANALYZE VERBOSE`. + +### 5.2 What is the difference between WALL TIME and CPU TIME? + +CPU time refers to the time the program actually occupies the CPU for computation during execution, representing the processor resources actually consumed by the program. + +Wall time refers to the real physical time from when the program starts execution to when it ends, including resource waiting time. + +`WALL TIME < CPU TIME` is common in parallel execution scenarios. For example, if a query fragment is executed in parallel by two threads, 10 seconds of physical time pass, but the two threads each occupy one CPU core running for 10 seconds, then the CPU TIME is about 20 seconds, and the WALL TIME is about 10 seconds. + +`WALL TIME > CPU TIME` is common in resource waiting scenarios. For example, a query fragment enters the blocked queue due to insufficient memory or waiting for upstream data, or enters the ready queue due to insufficient query thread resources. During the wait, no CPU is occupied, but physical time still passes. + +### 5.3 Does EXPLAIN ANALYZE have additional overhead? + +The statistics collection itself has almost no significant additional overhead. The `EXPLAIN ANALYZE` operator collects the statistics already available from the original query, and traverses via `next` without printing the query results, so there is usually no significant difference in time consumption compared to directly executing the same query. + +Note that the analyzed query is still fully executed and normally consumes CPU, memory, and IO resources. When evaluating the impact on the production environment, refer to the resource consumption of the original query itself. + +### 5.4 Which metrics should I focus on for IO time consumption? + +Metrics involving IO time consumption mainly include `loadBloomFilterActualIOSize`, `loadBloomFilterTime`, `loadTimeSeriesMetadataAlignedDiskSeqTime`, `loadTimeSeriesMetadataAlignedDiskUnseqTime`, `loadTimeSeriesMetadataActualIOSize`, `alignedTimeSeriesMetadataModificationTime`, `constructAlignedChunkReadersDiskTime`, and `loadChunkActualIOSize`. + +TimeSeriesMetadata loading statistics are separated for sequential and out-of-order files, but Chunk reading is not yet separately counted for sequential and out-of-order proportions; this can be estimated through the sequential/out-of-order ratio of TimeSeriesMetadata. + +### 5.5 How to observe the impact of out-of-order data on query performance? + +Out-of-order data mainly produces two types of impact: + +1. During query, an additional merge sort needs to be done in memory. + +2. Out-of-order data produces time range overlaps between data blocks, causing some statistics to be unusable for directly skipping data blocks that do not meet conditions, or for directly calculating aggregate values. + +Currently, there is no direct observation metric specifically for the impact of out-of-order data. Usually, you can execute a query once when out-of-order data exists, and then execute the query again after the out-of-order data is merged, and evaluate by comparing the time consumption before and after. diff --git a/src/UserGuide/latest-Table/SQL-Manual/SQL-Maintenance-Statements_apache.md b/src/UserGuide/latest-Table/SQL-Manual/SQL-Maintenance-Statements_apache.md index 9909c55e5..89dd859cd 100644 --- a/src/UserGuide/latest-Table/SQL-Manual/SQL-Maintenance-Statements_apache.md +++ b/src/UserGuide/latest-Table/SQL-Manual/SQL-Maintenance-Statements_apache.md @@ -494,7 +494,7 @@ IoTDB> KILL ALL QUERIES; -- teminate all query EXPLAIN ``` -Detailed syntax reference: [EXPLAIN STATEMENT](../User-Manual/Query-Performance-Analysis.md#_1-explain-statement) +Detailed syntax reference: [EXPLAIN STATEMENT](../User-Manual/Query-Performance-Analysis_apache.md#_2-explain) **Example:** @@ -543,7 +543,7 @@ IoTDB> explain select * from t1 EXPLAIN ANALYZE [VERBOSE] ``` -Detailed syntax reference: [EXPLAIN ANALYZE STATEMENT](../User-Manual/Query-Performance-Analysis.md#_2-explain-analyze-statement) +Detailed syntax reference: [EXPLAIN ANALYZE STATEMENT](../User-Manual/Query-Performance-Analysis_apache.md#_3-explain-analyze) **Example:** diff --git a/src/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis.md b/src/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis.md deleted file mode 100644 index 17c0259f0..000000000 --- a/src/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis.md +++ /dev/null @@ -1,392 +0,0 @@ - -# Query Performance Analysis - -Query analysis helps users understand query execution mechanisms and identify performance bottlenecks, facilitating optimization and improving efficiency. This directly enhances user experience and resource utilization. IoTDB provides two query analysis statements: **`EXPLAIN`** and **`EXPLAIN ANALYZE`**. - -- **`EXPLAIN`**: Displays the query execution plan, detailing how IoTDB retrieves and processes data. -- **`EXPLAIN ANALYZE`**: Executes the query and provides detailed performance metrics, such as execution time and resource consumption. Unlike other diagnostic tools, it requires no deployment and focuses on single-query analysis for precise troubleshooting. - -**Performance Analysis Methods Comparison** - -| **Method** | **Installation Difficulty** | **Business Impact** | **Functional Scope** | -| :----------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- | -| **EXPLAIN ANALYZE** | **Low.** No additional components required; built-in SQL statement in IoTDB. | **Low.** Impacts only the analyzed query, with no effect on other workloads. | **Supports cluster systems.** Enables tracing for a single SQL query. | -| **Monitoring Dashboard** | **Medium.** Requires installation of IoTDB monitoring dashboard tool (IoTDB) and enabling monitoring services. | **Medium.** Metrics collection introduces additional overhead. | **Supports cluster systems.** Analyzes overall database query load and latency. | -| **Arthas Sampling** | **Medium.** Requires Java Arthas installation (may be restricted in internal networks; sometimes requires application restart). | **High.** May degrade response speed of online services due to CPU sampling. | **Does n****ot supports cluster systems.** Analyzes overall database query load and latency. | - - - -## 1. EXPLAIN Statement - -### 1.1 Syntax - -The `EXPLAIN` command allows users to view the execution plan of an SQL query. It presents the plan as a series of operators, illustrating how IoTDB processes the query. The syntax is as follows, where `` represents the target query: - -```SQL -EXPLAIN -``` - -### 1.2 Description - -The result of `EXPLAIN` includes information such as data access strategies, whether filtering conditions are pushed down, and the distribution of the query plan across different nodes. This provides users with a means to visualize the internal execution logic of the query. - -```SQL --- Create database -CREATE DATABASE test; - --- Create table -USE test; -CREATE TABLE t1 (device_id STRING ID, type STRING ATTRIBUTE, speed FLOAT); - --- Insert data -INSERT INTO t1(device_id, type, speed) VALUES('car_1', 'Model Y', 120.0); -INSERT INTO t1(device_id, type, speed) VALUES('car_2', 'Model 3', 100.0); - --- Execute EXPLAIN -EXPLAIN SELECT * FROM t1; -``` - -The result shows that IoTDB retrieves data from different data partitions through two `TableScan` nodes and aggregates the data using a `Collect` operator before returning it: - -```SQL -+-----------------------------------------------------------------------------------------------+ -| distribution plan| -+-----------------------------------------------------------------------------------------------+ -| ┌─────────────────────────────────────────────┐ | -| │OutputNode-4 │ | -| │OutputColumns-[time, device_id, type, speed] │ | -| │OutputSymbols: [time, device_id, type, speed]│ | -| └─────────────────────────────────────────────┘ | -| │ | -| │ | -| ┌─────────────────────────────────────────────┐ | -| │Collect-21 │ | -| │OutputSymbols: [time, device_id, type, speed]│ | -| └─────────────────────────────────────────────┘ | -| ┌───────────────────────┴───────────────────────┐ | -| │ │ | -|┌─────────────────────────────────────────────┐ ┌───────────┐ | -|│TableScan-19 │ │Exchange-28│ | -|│QualifiedTableName: test.t1 │ └───────────┘ | -|│OutputSymbols: [time, device_id, type, speed]│ │ | -|│DeviceNumber: 1 │ │ | -|│ScanOrder: ASC │ ┌─────────────────────────────────────────────┐| -|│PushDownOffset: 0 │ │TableScan-20 │| -|│PushDownLimit: 0 │ │QualifiedTableName: test.t1 │| -|│PushDownLimitToEachDevice: false │ │OutputSymbols: [time, device_id, type, speed]│| -|│RegionId: 2 │ │DeviceNumber: 1 │| -|└─────────────────────────────────────────────┘ │ScanOrder: ASC │| -| │PushDownOffset: 0 │| -| │PushDownLimit: 0 │| -| │PushDownLimitToEachDevice: false │| -| │RegionId: 1 │| -| └─────────────────────────────────────────────┘| -+-----------------------------------------------------------------------------------------------+ -``` - -## 2. EXPLAIN ANALYZE Statement - -### 2.1 Syntax - -The `EXPLAIN ANALYZE` statement provides detailed performance metrics by executing the query and analyzing its runtime behavior. The syntax is as follows: - -```SQL -EXPLAIN ANALYZE [VERBOSE] -``` - -- `SELECT_STATEMENT` corresponds to the query statement to be analyzed. -- `VERBOSE` (optional): Prints detailed analysis results. Without this option, some metrics are omitted. - -### 2.2 Description - -`Explain Analyze` is a performance analysis SQL built into the IoTDB query engine. Unlike `Explain`, it runs the query and collects execution metrics, enabling users to trace performance bottlenecks, observe resource usage, and conduct precise performance tuning. - -The output includes detailed statistics such as query planning time, execution time, data partitioning, and resource consumption: - -![](/img/explain-analyze-1.png) - -### 2.3 Detailed Breakdown of EXPLAIN ANALYZE Results - -#### QueryStatistics - -`QueryStatistics` contains high-level statistics about the query execution, including the time spent in each planning stage and the number of query shards. - -- Analyze Cost: Time spent in the SQL analysis phase, including `FetchPartitionCost` and `FetchSchemaCost`. -- Fetch Partition Cost: Time spent fetching partition tables. -- Fetch Schema Cost: Time spent fetching schema and performing permission checks. -- Logical Plan Cost: Time spent building the logical plan. -- Logical Optimization Cost: Time spent optimizing the logical plan. -- Distribution Plan Cost: Time spent building the distributed execution plan. -- Fragment Instance Count: The total number of query shards. Each shard's information is output individually. - -#### FragmentInstance - -A `FragmentInstance` is a wrapper for a query shard in IoTDB. Each query shard outputs its execution information in the result set, including `FragmentStatistics` and operator information. `FragmentStatistics` provides detailed metrics about the shard's execution, including: - -- Total Wall Time: The physical time from the start to the end of the shard's execution. -- Cost of initDataQuerySource: Time spent building the query file list. -- Seq File (unclosed): Number of unclosed (memtable) sequential files. -- Seq File (closed): Number of closed sequential files. -- UnSeq File (unclosed): Number of unclosed (memtable) unsequential files. -- UnSeq File (closed): Number of closed unsequential files. -- Ready Queued Time: Total time the shard's tasks spent in the ready queue (tasks are not blocked but lack query execution thread resources). -- Blocked Queued Time: Total time the shard's tasks spent in the blocked queue (tasks are blocked due to resources like memory or upstream data not being sent). - -Since V2.0.9, the following information will be added to `FragmentInstance`: - -- `OutputPlanNodeId`: Indicates the downstream node that receives data corresponding to the sink node. Only present in sink nodes. -- `sizeInBytes`: Represents the size in bytes of TsBlocks received in exchange nodes (only data size is counted). Only present in exchange nodes. -- Data filtering‑related fields for `tableScan` nodes. Valid only when filter pushdown is applied to tableScan, and only present in tableScan nodes: - - `TimeSeriesIndexFilteredRows`: Number of rows filtered out by TimeseriesMetadata - - `ChunkIndexFilteredRows`: Number of rows filtered out by ChunkMetadata - - `PageIndexFilteredRows`: Number of rows filtered out by PageHeader internal filtering - - `RowScanFilteredRows`: Number of rows filtered out during per-row data inspection. Only displayed when `verbose` is enabled. - - -#### BloomFilter-Related Metrics - -Bloom filters help determine if a sequence exists in a TsFile. They are stored at the end of each TsFile. - -- loadBloomFilterFromCacheCount: Number of times the BloomFilterCache was hit. -- loadBloomFilterFromDiskCount: Number of times BloomFilter was read from disk. -- loadBloomFilterActualIOSize: Disk I/O used when reading BloomFilter (in bytes). -- loadBloomFilterTime: Total time spent reading BloomFilter and checking if a sequence exists (in milliseconds). - -#### TimeSeriesMetadata-Related Metrics - -TimeSeriesMetadata contains indexing information for sequences in a TsFile. Each TsFile has one metadata entry per sequence. - -- loadTimeSeriesMetadataDiskSeqCount: Number of TimeSeriesMetadata entries loaded from closed sequential files. - - Usually equals the number of closed sequential files but may be lower if operators like `LIMIT` are applied. -- loadTimeSeriesMetadataDiskUnSeqCount: Number of TimeSeriesMetadata entries loaded from closed unsequential files. -- loadTimeSeriesMetadataDiskSeqTime: Time spent loading sequential TimeSeriesMetadata from disk. - - Not all loads involve disk I/O, as cache hits may reduce access time. -- loadTimeSeriesMetadataDiskUnSeqTime: Time spent loading unsequential TimeSeriesMetadata from disk. -- loadTimeSeriesMetadataFromCacheCount: Number of cache hits when accessing TimeSeriesMetadata. -- loadTimeSeriesMetadataFromDiskCount: Number of times TimeSeriesMetadata was read from disk. -- loadTimeSeriesMetadataActualIOSize: Disk I/O used for TimeSeriesMetadata reads (in bytes). - -#### Mods File-Related Metrics - -- TimeSeriesMetadataModificationTime: Time spent reading modification (mods) files. - -#### Chunk-Related Metrics - -Chunks are the fundamental units of data storage in TsFiles. - -- constructAlignedChunkReadersDiskCount: Number of Chunks read from closed TsFiles. -- constructAlignedChunkReadersDiskTime: Total time spent reading Chunks from closed TsFiles (including disk IO and decompression). -- pageReadersDecodeAlignedDiskCount: Number of pages decoded from closed TsFiles. -- pageReadersDecodeAlignedDiskTime: Total time spent decoding pages from closed TsFiles. -- loadChunkFromCacheCount: Number of times ChunkCache was hit. - - For aligned devices, each component (including the time column) requests the cache separately. Therefore, `(loadChunkFromCacheCount + loadChunkFromDiskCount)` equals `tsfile number * subSensor number (including the time column) * avg chunk number in each TsFile`. -- loadChunkFromDiskCount: Number of times Chunks were read from disk. -- loadChunkActualIOSize: Disk I/O used when reading Chunks (in bytes). - -### 2.4 Special Notes - -**Query** **Timeout Scenario with EXPLAIN ANALYZE** - -Since `EXPLAIN ANALYZE` runs as a special query type, it cannot return results if execution times out. To aid troubleshooting, IoTDB automatically enables a timed logging mechanism that periodically records partial results to a dedicated log file (`logs/log_explain_analyze.log`). This mechanism requires no user configuration. - -- The logging interval is dynamically calculated based on the query's timeout duration, ensuring at least two log entries before the timeout occurs. -- Users can examine the log file to identify potential causes of the timeout. - -### 2.5 Example - -The following example demonstrates how to use `EXPLAIN ANALYZE`: - -```SQL --- Create Database -CREATE DATABASE test; - --- Create Table -USE test; -CREATE TABLE t1 (device_id STRING ID, type STRING ATTRIBUTE, speed FLOAT); - --- Insert Data -INSERT INTO t1(device_id, type, speed) VALUES('car_1', 'Model Y', 120.0); -INSERT INTO t1(device_id, type, speed) VALUES('car_2', 'Model 3', 100.0); - --- Execute EXPLAIN ANALYZE -EXPLAIN ANALYZE VERBOSE SELECT * FROM t1; -``` - -**Output** **(Simplified):** - -```SQL -+-----------------------------------------------------------------------------------------------+ -| Explain Analyze| -+-----------------------------------------------------------------------------------------------+ -|Analyze Cost: 38.860 ms | -|Fetch Partition Cost: 9.888 ms | -|Fetch Schema Cost: 54.046 ms | -|Logical Plan Cost: 10.102 ms | -|Logical Optimization Cost: 17.396 ms | -|Distribution Plan Cost: 2.508 ms | -|Dispatch Cost: 22.126 ms | -|Fragment Instances Count: 2 | -| | -|FRAGMENT-INSTANCE[Id: 20241127_090849_00009_1.2.0][IP: 0.0.0.0][DataRegion: 2][State: FINISHED]| -| Total Wall Time: 18 ms | -| Cost of initDataQuerySource: 6.153 ms | -| Seq File(unclosed): 1, Seq File(closed): 0 | -| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | -| ready queued time: 0.164 ms, blocked queued time: 0.342 ms | -| Query Statistics: | -| loadBloomFilterFromCacheCount: 0 | -| loadBloomFilterFromDiskCount: 0 | -| loadBloomFilterActualIOSize: 0 | -| loadBloomFilterTime: 0.000 | -| loadTimeSeriesMetadataAlignedMemSeqCount: 1 | -| loadTimeSeriesMetadataAlignedMemSeqTime: 0.246 | -| loadTimeSeriesMetadataFromCacheCount: 0 | -| loadTimeSeriesMetadataFromDiskCount: 0 | -| loadTimeSeriesMetadataActualIOSize: 0 | -| constructAlignedChunkReadersMemCount: 1 | -| constructAlignedChunkReadersMemTime: 0.294 | -| loadChunkFromCacheCount: 0 | -| loadChunkFromDiskCount: 0 | -| loadChunkActualIOSize: 0 | -| pageReadersDecodeAlignedMemCount: 1 | -| pageReadersDecodeAlignedMemTime: 0.047 | -| [PlanNodeId 43]: IdentitySinkNode(IdentitySinkOperator) | -| CPU Time: 5.523 ms | -| output: 2 rows | -| HasNext() Called Count: 6 | -| Next() Called Count: 5 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 31]: CollectNode(CollectOperator) | -| CPU Time: 5.512 ms | -| output: 2 rows | -| HasNext() Called Count: 6 | -| Next() Called Count: 5 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 29]: TableScanNode(TableScanOperator) | -| CPU Time: 5.439 ms | -| output: 1 rows | -| HasNext() Called Count: 3 -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| DeviceNumber: 1 | -| CurrentDeviceIndex: 0 | -| [PlanNodeId 40]: ExchangeNode(ExchangeOperator) | -| CPU Time: 0.053 ms | -| output: 1 rows | -| HasNext() Called Count: 2 | -| Next() Called Count: 1 | -| Estimated Memory Size: : 131072 | -| | -|FRAGMENT-INSTANCE[Id: 20241127_090849_00009_1.3.0][IP: 0.0.0.0][DataRegion: 1][State: FINISHED]| -| Total Wall Time: 13 ms | -| Cost of initDataQuerySource: 5.725 ms | -| Seq File(unclosed): 1, Seq File(closed): 0 | -| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | -| ready queued time: 0.118 ms, blocked queued time: 5.844 ms | -| Query Statistics: | -| loadBloomFilterFromCacheCount: 0 | -| loadBloomFilterFromDiskCount: 0 | -| loadBloomFilterActualIOSize: 0 | -| loadBloomFilterTime: 0.000 | -| loadTimeSeriesMetadataAlignedMemSeqCount: 1 | -| loadTimeSeriesMetadataAlignedMemSeqTime: 0.004 | -| loadTimeSeriesMetadataFromCacheCount: 0 | -| loadTimeSeriesMetadataFromDiskCount: 0 | -| loadTimeSeriesMetadataActualIOSize: 0 | -| constructAlignedChunkReadersMemCount: 1 | -| constructAlignedChunkReadersMemTime: 0.001 | -| loadChunkFromCacheCount: 0 | -| loadChunkFromDiskCount: 0 | -| loadChunkActualIOSize: 0 | -| pageReadersDecodeAlignedMemCount: 1 | -| pageReadersDecodeAlignedMemTime: 0.007 | -| [PlanNodeId 42]: IdentitySinkNode(IdentitySinkOperator) | -| CPU Time: 0.270 ms | -| output: 1 rows | -| HasNext() Called Count: 3 | -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 30]: TableScanNode(TableScanOperator) | -| CPU Time: 0.250 ms | -| output: 1 rows | -| HasNext() Called Count: 3 | -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| DeviceNumber: 1 | -| CurrentDeviceIndex: 0 | -+-----------------------------------------------------------------------------------------------+ -``` - -### 2.6 FAQs - -#### Differences Between WALL TIME and CPU TIME - -- **CPU** **TIME** (also known as processor time or CPU utilization time) refers to the actual time a program spends executing on the CPU, reflecting the amount of CPU resources consumed. -- **WALL TIME** (also known as elapsed time or physical time) refers to the total time from the start to the end of a program's execution, including all waiting periods. - -**Key Scenarios:** - -1. WALL TIME < CPU TIME: This occurs when multiple threads are used for parallel execution. For example, if a query shard runs for 10 seconds in real-world time using two threads, the CPU time would be 20 seconds (10 seconds × 2 threads), while the wall time would remain 10 seconds. -2. WALL TIME > CPU TIME: This typically happens due to resource contention or insufficient query threads: - 1. Blocked by Resource Constraints: If a query shard is blocked (e.g., due to insufficient memory for data transfer or waiting for upstream data), it remains in the Blocked Queue, accumulating wall time without consuming CPU time. - 2. Insufficient Query Threads: If 20 query shards are running concurrently but only 16 query threads are available, 4 shards will be placed in the Ready Queue, waiting for execution. During this waiting period, wall time continues to elapse, but no CPU time is consumed. - -#### Additional Considerations for EXPLAIN ANALYZE - -`EXPLAIN ANALYZE` introduces minimal overhead, as it runs in a separate thread to collect query statistics. These metrics are generated by the system regardless of whether `EXPLAIN ANALYZE` is executed; the command simply retrieves them for user inspection. - -Additionally, `EXPLAIN ANALYZE` iterates through the result set without producing output. Therefore, the reported execution time closely reflects the actual query execution time, with negligible deviation. - -#### Key Metrics for I/O Time - -The following metrics are crucial for evaluating I/O performance during query execution: - -- `loadBloomFilterActualIOSize` -- `loadBloomFilterTime` -- `loadTimeSeriesMetadataAlignedDisk[Seq/Unseq]Time` -- `loadTimeSeriesMetadataActualIOSize` -- `alignedTimeSeriesMetadataModificationTime` -- `constructAlignedChunkReadersDiskTime` -- `loadChunkActualIOSize` - -These metrics were detailed in previous sections. While TimeSeriesMetadata loading is tracked separately for sequential and unsequential files, chunk reading is not currently differentiated. However, the proportion of sequential versus unsequential data can be inferred from TimeSeriesMetadata statistics. - -#### Impact of Unsequential Data on Query Performance - -Unsequential data can negatively affect query performance in the following ways: - -1. **Additional Merge Sort in Memory**: Unsequential data requires an additional merge sort in memory, which is generally a short operation since it involves pure CPU operations. -2. **Overlap in Time Ranges**: Unsequential data can cause time-range overlaps between data chunks, rendering statistical information unusable: - 1. Inability to Skip Chunks: If a query involves value filtering conditions, statistical information cannot be used to skip entire chunks that do not meet the criteria. However, this is less impactful if the query only involves time filtering. - 2. Inability to Compute Aggregates Directly: Statistical information cannot be used to directly compute aggregate values without reading the data. - -Currently, there is no direct method to measure the performance impact of unsequential data. The only approach is to compare query performance before and after merging the unsequential data. However, even after merging, the query still incurs I/O, compression, and decoding overhead, meaning the execution time will not decrease significantly. - -#### Why EXPLAIN ANALYZE Results Are Not Logged on Query Timeout - -If `EXPLAIN ANALYZE` results are missing from the `log_explain_analyze.log` file after a query timeout, the issue may stem from an incomplete system upgrade. Specifically, if the lib package was updated without updating the conf/logback-datanode.xml file, the logging configuration may be outdated. - -**Solution:** - -1. Replace the `conf/logback-datanode.xml` file with the updated version. -2. A system restart is not required, as the new configuration will be hot-loaded automatically. -3. Wait approximately one minute, then re-execute the `EXPLAIN ANALYZE` statement with the `VERBOSE` option to confirm proper logging. \ No newline at end of file diff --git a/src/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis_apache.md b/src/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis_apache.md new file mode 100644 index 000000000..d45af303b --- /dev/null +++ b/src/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis_apache.md @@ -0,0 +1,1829 @@ + +# Query Performance Analysis + +## 1. Overview + +Query analysis helps users understand the execution mechanism and performance bottlenecks of queries, enabling query optimization and performance tuning. The IoTDB table model provides two types of query analysis statements: + +- `EXPLAIN`: Previews the execution plan of a query SQL, showing how IoTDB organizes data retrieval and processing. + +- `EXPLAIN ANALYZE`: Actually executes the query on top of `EXPLAIN`, and displays the time, resource consumption, and operator statistics during query execution. + +The default output of `EXPLAIN` and `EXPLAIN ANALYZE` is designed for human reading and is suitable for direct viewing in the CLI. For scenarios that require stable parsing of query plans or execution statistics, such as Web Console, CI regression testing, and automated performance diagnosis systems, you can use the JSON output format to obtain structured results. The JSON format is supported since V2.0.11. + +### 1.1 Comparison of Query Analysis Methods + +Compared with Arthas sampling and other troubleshooting methods, `EXPLAIN ANALYZE` requires no additional components and can perform distributed tracing on a single SQL, making it more suitable for locating performance issues of specific queries. + +|Method|Installation Difficulty|Business Impact|Functional Scope| +|---|---|---|---| +|`EXPLAIN ANALYZE` statement|Low. No additional components required, built-in IoTDB SQL statement|Low. Only affects the single query being analyzed, no impact on other online workloads|Supports distributed, can trace a single SQL| +|Arthas sampling|Medium. Requires installing the Java Arthas tool|High. CPU sampling may affect online business response speed|Does not support distributed, only supports analysis of overall query load and time consumption of the database| + +## 2. EXPLAIN + +### 2.1 Syntax + +The `EXPLAIN` command is used to view the distributed execution plan of a SQL query. The execution plan is displayed as an operator tree, describing how IoTDB will execute the query. + +```SQL +EXPLAIN [(FORMAT { GRAPHVIZ | JSON })] +``` + +Where: + +|Parameter|Description| +|---|---| +|`SELECT_STATEMENT`|The query statement to be analyzed| +|`FORMAT GRAPHVIZ`|Outputs the distributed plan in the default graphical text form, suitable for human reading| +|`FORMAT JSON`|Outputs the distributed plan as a JSON object, suitable for programmatic parsing (supported since V2.0.11)| + +The format name after `FORMAT` is case-insensitive, for example, `FORMAT json` is equivalent to `FORMAT JSON`. + +### 2.2 Default GRAPHVIZ Format + +The default format of `EXPLAIN` is `GRAPHVIZ`. After execution, you will get the `distribution plan` result column. + +Taking `table1` in the [sample data](../Reference/Sample-Data.md) as an example, the following two statements are equivalent: + +```SQL +EXPLAIN SELECT * FROM table1; +EXPLAIN (FORMAT GRAPHVIZ) SELECT * FROM table1; +``` + +After executing the above statements, the output is as follows: IoTDB reads data from different data partitions through `DeviceTableScanNode` nodes, and aggregates and returns the results through the `Collect` operator. + +```SQL ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| distribution plan| ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | +| │OutputNode-4 │ | +| │OutputColumns-[time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time] │ | +| │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ | +| └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | +| │ | +| │ | +| ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | +| │Collect-33 │ | +| │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ | +| └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | +| ┌───────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────┐ | +| │ │ | +| ┌───────────┐ ┌───────────┐ | +| │Exchange-40│ │Exchange-41│ | +| └───────────┘ └───────────┘ | +| │ │ | +| │ │ | +|┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐| +|│DeviceTableScanNode-32 │ │DeviceTableScanNode-31 │| +|│QualifiedTableName: database1.table1 │ │QualifiedTableName: database1.table1 │| +|│OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│| +|│DeviceNumber: 4 │ │DeviceNumber: 2 │| +|│ScanOrder: ASC │ │ScanOrder: ASC │| +|│PushDownOffset: 0 │ │PushDownOffset: 0 │| +|│PushDownLimit: 0 │ │PushDownLimit: 0 │| +|│PushDownLimitToEachDevice: false │ │PushDownLimitToEachDevice: false │| +|│RegionId: 1 │ │RegionId: 2 │| +|└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘| ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +``` + +### 2.3 JSON Format + +Using `EXPLAIN (FORMAT JSON)` returns the JSON representation of the distributed plan tree. The result set is a single-column output, and in JSON format returns a single row with a single JSON object, making it convenient for JDBC clients or automation tools to read directly. + +The top-level structure of `EXPLAIN (FORMAT JSON)` varies with the query type: + +|Scenario|Top-level Structure|Description| +|---|---|---| +|Regular query|plan node JSON object|The top-level is directly a plan node object such as `OutputNode`| +|Query containing materialized CTE|wrapper JSON object|The top-level contains `cteQueries` and `mainQuery`, used to retain both the CTE subquery plan and the main query plan| + +Each plan node is output as a JSON object. The basic fields are as follows: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`name`|string|The display name formed by concatenating the node type and plan node id, for example `OutputNode-4`| +|`id`|string|plan node id| +|`properties`|object|Node properties. Only output when the node has displayable properties| +|`children`|array|Array of child nodes. Only output when the node has child nodes| + +Common plan node properties include: + +|Node Type|Main Fields/Properties| +|---|---| +|`OutputNode`|`OutputColumns`, `OutputSymbols`| +|`TableScanNode` / `DeviceTableScanNode`|`QualifiedTableName`, `OutputSymbols`, `DeviceNumber`, `ScanOrder`, `TimePredicate`, `PushDownPredicate`, `PushDownOffset`, `PushDownLimit`, `PushDownLimitToEachDevice`, `RegionId`| +|`TreeDeviceViewScanNode`|In addition to table scan properties, adds `TreeDB`, `MeasurementToColumnName`| +|`AggregationNode`|`OutputSymbols`, `Aggregators`, `GroupingKeys`, `Streamable`, `PreGroupedSymbols`, `Step`| +|`FilterNode`|`Predicate`| +|`ProjectNode`|`OutputSymbols`, `Expressions`| +|`LimitNode` / `OffsetNode`|`Count`| +|`SortNode` / `MergeSortNode`|`OrderBy`| +|`JoinNode`|`JoinType`, `Criteria`, `OutputSymbols`| +|`UnionNode`|`OutputSymbols`| +|`ExplainAnalyzeNode`|`ChildPermittedOutputs`| + +When the query contains a materialized CTE, the JSON top-level is no longer a single plan node directly, but is wrapped as: + +```JSON +{ + "cteQueries": [ + { + "name": "cte1", + "plan": { + "name": "OutputNode-", + "id": "" + } + } + ], + "mainQuery": { + "name": "OutputNode-", + "id": "" + } +} +``` + +Where `cteQueries[].plan` and `mainQuery` are both plan node JSON objects. This structure is used to retain both the CTE subquery plan and the main query plan. + +Taking `table1` in the [sample data](../Reference/Sample-Data.md) as an example: + +```SQL +EXPLAIN (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "CollectNode-33", + "id": "33", + "children": [ + { + "name": "ExchangeNode-40", + "id": "40", + "children": [ + { + "name": "DeviceTableScanNode-32", + "id": "32", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-41", + "id": "41", + "children": [ + { + "name": "DeviceTableScanNode-31", + "id": "31", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] +} ++-----------------+ +``` + +## 3. EXPLAIN ANALYZE + +### 3.1 Syntax + +`EXPLAIN ANALYZE` is a performance analysis SQL built into the IoTDB query engine. Unlike `EXPLAIN`, it actually executes the corresponding query plan and collects execution information, which can be used to track the specific performance distribution of a query, assisting in resource observation, performance tuning, and exception analysis. + +```SQL +EXPLAIN ANALYZE [VERBOSE] [(FORMAT { TEXT | JSON })] +``` + +Where: + +|Parameter|Description| +|---|---| +|`SELECT_STATEMENT`|The query statement to be analyzed| +|`VERBOSE`|Prints more detailed analysis results. When not specified, some fine-grained statistics are omitted| +|`FORMAT TEXT`|Outputs analysis results in the default text form, suitable for human reading| +|`FORMAT JSON`|Outputs analysis results as a JSON object, suitable for programmatic parsing (supported since V2.0.11)| + +### 3.2 Default TEXT Format + +The default format of `EXPLAIN ANALYZE` is `TEXT`. After execution, you will get the `Explain Analyze` result column. The results consist of query planning statistics, fragment instance statistics, data reading statistics, and operator tree statistics. + +- **Query Planning Statistics** + +`QueryStatistics` contains query-level statistics: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`Analyze Cost`|Query planning statistics|Time consumed in the SQL analysis phase| +|`Fetch Partition Cost`|Query planning statistics|Time consumed fetching the partition table| +|`Fetch Schema Cost`|Query planning statistics|Time consumed fetching metadata and permission validation| +|`Logical Plan Cost`|Query planning statistics|Time consumed building the logical plan| +|`Logical Optimization Cost`|Query planning statistics|Time consumed in logical plan optimization| +|`Distribution Plan Cost`|Query planning statistics|Time consumed building the distributed plan| +|`Dispatch Cost`|Query planning statistics|Time consumed dispatching fragment instances| +|`Fragment Instances Count`|Query planning statistics|Total number of query fragments; each fragment's information is output in sequence| + +- **Fragment Instance Statistics** + +`FragmentInstance` is the encapsulation of an IoTDB query fragment. Each query fragment outputs a copy of execution information, mainly including fragment statistics and operator information. + +Fragment statistics include: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`Total Wall Time`|Fragment statistics|Physical time from when the fragment starts execution to when it finishes| +|`Cost of initDataQuerySource`|Fragment statistics|Time consumed building the query file list| +|`Seq File(unclosed)`|Fragment statistics|Number of unclosed sequential files| +|`Seq File(closed)`|Fragment statistics|Number of closed sequential files| +|`UnSeq File(unclosed)`|Fragment statistics|Number of unclosed out-of-order files| +|`UnSeq File(closed)`|Fragment statistics|Number of closed out-of-order files| +|`ready queued time`|Fragment statistics|Total time all tasks of the query fragment spent in the ready queue| +|`blocked queued time`|Fragment statistics|Total time all tasks of the query fragment spent in the blocked queue| + +- **Data Reading Statistics** + +`Query Statistics` detail fields include: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`loadBloomFilterFromCacheCount`|BloomFilter|Number of BloomFilterCache hits| +|`loadBloomFilterFromDiskCount`|BloomFilter|Number of times BloomFilter was read from disk| +|`loadBloomFilterActualIOSize`|BloomFilter|Disk IO generated when reading BloomFilter from disk, in bytes| +|`loadBloomFilterTime`|BloomFilter|Total time spent reading BloomFilter and computing whether a series exists, in ms| +|`loadTimeSeriesMetadataDiskSeqCount`|TimeSeriesMetadata|Number of TimeSeriesMetadata loaded from closed sequential files| +|`loadTimeSeriesMetadataDiskUnSeqCount`|TimeSeriesMetadata|Number of TimeSeriesMetadata loaded from closed out-of-order files| +|`loadTimeSeriesMetadataDiskSeqTime`|TimeSeriesMetadata|Time consumed loading TimeSeriesMetadata from closed sequential files| +|`loadTimeSeriesMetadataDiskUnSeqTime`|TimeSeriesMetadata|Time consumed loading TimeSeriesMetadata from closed out-of-order files| +|`loadTimeSeriesMetadataFromCacheCount`|TimeSeriesMetadata|Number of TimeSeriesMetadataCache hits| +|`loadTimeSeriesMetadataFromDiskCount`|TimeSeriesMetadata|Number of times TimeSeriesMetadata was read from disk| +|`loadTimeSeriesMetadataActualIOSize`|TimeSeriesMetadata|Disk IO generated when reading TimeSeriesMetadata from disk, in bytes| +|`TimeSeriesMetadataModificationTime`|Mods file|Time consumed reading mods files| +|`constructAlignedChunkReadersDiskCount`|Chunk|Number of times ChunkReader was constructed| +|`constructAlignedChunkReadersDiskTime`|Chunk|Total time consumed constructing ChunkReader, including disk IO and decompression| +|`pageReadersDecodeAlignedDiskCount`|Chunk|Number of pages decoded| +|`pageReadersDecodeAlignedDiskTime`|Chunk|Total time consumed decoding pages| +|`loadChunkFromCacheCount`|Chunk|Number of ChunkCache hits| +|`loadChunkFromDiskCount`|Chunk|Number of times Chunk was read from disk| +|`loadChunkActualIOSize`|Chunk|Disk IO generated when reading Chunk from disk, in bytes| + +- **Operator Tree Statistics** + +Operator tree statistics include: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`CPU Time`|Operator tree statistics|CPU execution time of the current operator| +|`output`|Operator tree statistics|Number of output rows of the current operator| +|`HasNext() Called Count`|Operator tree statistics|Number of times the current operator's `HasNext()` interface was called| +|`Next() Called Count`|Operator tree statistics|Number of times the current operator's `Next()` interface was called| +|`Estimated Memory Size`|Operator tree statistics|Estimated memory usage of the current operator| +|Node-specific statistics|Operator tree statistics|Custom statistics fields output by different operators, such as `DeviceNumber`, `CurrentDeviceIndex`, `OutputPlanNodeId`, `size_in_bytes`, etc.| + +Common node-specific statistics fields include: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`DeviceNumber`|`TableScan` related nodes|Number of devices involved in the current table scan| +|`CurrentDeviceIndex`|`TableScan` related nodes|Index of the device currently being scanned| +|`OutputPlanNodeId`|sink node|The downstream data-receiving node corresponding to the sink node| +|`size_in_bytes`|exchange node|Number of bytes of TsBlock received by the exchange node, only counting data size| +|`TimeSeriesIndexFilteredRows`|table scan node when filter is pushed down to table scan|Number of data rows filtered out by series metadata| +|`ChunkIndexFilteredRows`|table scan node when filter is pushed down to table scan|Number of data rows filtered out by chunk metadata| +|`PageIndexFilteredRows`|table scan node when filter is pushed down to table scan|Number of data rows filtered out by page header internal statistics| +|`RowScanFilteredRows`|table scan node when filter is pushed down to table scan and `VERBOSE` is used|Number of rows filtered out during row-by-row data checking| + +The actual output fields vary with the query type, the operators involved, whether `VERBOSE` is used, and whether filter pushdown is triggered. When the corresponding logic is not triggered, the relevant fields may not appear in the result. + +- **Text Output Example** + +Taking `table1` in the [sample data](../Reference/Sample-Data.md) as an example, the following two statements are equivalent: + +```SQL +EXPLAIN ANALYZE SELECT * FROM table1; +EXPLAIN ANALYZE (FORMAT TEXT) SELECT * FROM table1; +``` + +```SQL ++-------------------------------------------------------------------------------------------------------------------------+ +| Explain Analyze| ++-------------------------------------------------------------------------------------------------------------------------+ +|Analyze Cost: 3.862 ms | +|Fetch Partition Cost: 0.785 ms | +|Fetch Schema Cost: 5.473 ms | +|Logical Plan Cost: 37.350 ms | +|Logical Optimization Cost: 4.340 ms | +|Distribution Plan Cost: 1.287 ms | +|Dispatch Cost: 8.711 ms | +|Fragment Instances Count: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.2.0][IP: 127.0.0.1:10730][DataRegion: virtual_data_region][State: FINISHED]| +| Total Wall Time: 64 ms | +| Cost of initDataQuerySource: 0.000 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.428 ms, blocked queued time: 11.410 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 69]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 28.691 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| DownStreamPlanNodeId: 66 | +| [PlanNodeId 55]: CollectNode(CollectOperator) | +| CPU Time: 28.656 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| [PlanNodeId 64]: ExchangeNode(ExchangeOperator) | +| CPU Time: 13.310 ms | +| output: 12 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 3264 | +| [PlanNodeId 65]: ExchangeNode(ExchangeOperator) | +| CPU Time: 15.286 ms | +| output: 6 rows | +| HasNext() Called Count: 3 | +| Next() Called Count: 2 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 1632 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.3.0][IP: 127.0.0.1:10730][DataRegion: 1][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 7.652 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 2, UnSeq File(closed): 0 | +| ready queued time: 0.089 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 67]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 2.007 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 64 | +| [PlanNodeId 54]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 1.981 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 4 | +| CurrentDeviceIndex: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.4.0][IP: 127.0.0.1:10730][DataRegion: 2][State: FINISHED] | +| Total Wall Time: 48 ms | +| Cost of initDataQuerySource: 0.933 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 1, UnSeq File(closed): 0 | +| ready queued time: 0.141 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 68]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 1.846 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 65 | +| [PlanNodeId 53]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 1.755 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 2 | +| CurrentDeviceIndex: 1 | ++-------------------------------------------------------------------------------------------------------------------------+ +``` + +- **VERBOSE Output Example** + +Using `VERBOSE` you can view finer-grained data reading and filtering statistics: + +```SQL +EXPLAIN ANALYZE VERBOSE SELECT * FROM table1; +EXPLAIN ANALYZE VERBOSE (FORMAT TEXT) SELECT * FROM table1; +``` + +```YAML ++-------------------------------------------------------------------------------------------------------------------------+ +| Explain Analyze| ++-------------------------------------------------------------------------------------------------------------------------+ +|Analyze Cost: 1.016 ms | +|Fetch Partition Cost: 0.555 ms | +|Fetch Schema Cost: 3.789 ms | +|Logical Plan Cost: 0.491 ms | +|Logical Optimization Cost: 2.625 ms | +|Distribution Plan Cost: 0.393 ms | +|Dispatch Cost: 37.041 ms | +|Fragment Instances Count: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.2.0][IP: 127.0.0.1:10730][DataRegion: virtual_data_region][State: FINISHED]| +| Total Wall Time: 43 ms | +| Cost of initDataQuerySource: 0.000 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.248 ms, blocked queued time: 23.757 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 0 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.000 | +| loadTimeSeriesMetadataFromCacheCount: 0 | +| loadTimeSeriesMetadataFromDiskCount: 0 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| loadChunkFromCacheCount: 0 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 69]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 0.988 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| DownStreamPlanNodeId: 66 | +| [PlanNodeId 55]: CollectNode(CollectOperator) | +| CPU Time: 0.954 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| [PlanNodeId 64]: ExchangeNode(ExchangeOperator) | +| CPU Time: 0.569 ms | +| output: 12 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 3264 | +| [PlanNodeId 65]: ExchangeNode(ExchangeOperator) | +| CPU Time: 0.354 ms | +| output: 6 rows | +| HasNext() Called Count: 3 | +| Next() Called Count: 2 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 1632 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.3.0][IP: 127.0.0.1:10730][DataRegion: 1][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 0.224 ms | +| Seq File(unclosed): 0, Seq File(closed): 2 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.025 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 2 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.018 | +| loadTimeSeriesMetadataAlignedDiskSeqCount: 4 | +| loadTimeSeriesMetadataAlignedDiskSeqTime: 0.189 | +| loadTimeSeriesMetadataFromCacheCount: 18 | +| loadTimeSeriesMetadataFromDiskCount: 2 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| alignedTimeSeriesMetadataModificationTime: 0.026 | +| constructAlignedChunkReadersDiskCount: 4 | +| constructAlignedChunkReadersDiskTime: 3.251 | +| loadChunkFromCacheCount: 18 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| pageReadersDecodeAlignedDiskCount: 4 | +| pageReadersDecodeAlignedDiskTime: 0.210 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 67]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 5.150 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 64 | +| [PlanNodeId 54]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 5.136 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 4 | +| CurrentDeviceIndex: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.4.0][IP: 127.0.0.1:10730][DataRegion: 2][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 0.397 ms | +| Seq File(unclosed): 0, Seq File(closed): 1 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.075 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 0 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.000 | +| loadTimeSeriesMetadataAlignedDiskSeqCount: 2 | +| loadTimeSeriesMetadataAlignedDiskSeqTime: 0.313 | +| loadTimeSeriesMetadataFromCacheCount: 10 | +| loadTimeSeriesMetadataFromDiskCount: 0 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| alignedTimeSeriesMetadataModificationTime: 0.046 | +| constructAlignedChunkReadersDiskCount: 2 | +| constructAlignedChunkReadersDiskTime: 2.307 | +| loadChunkFromCacheCount: 10 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| pageReadersDecodeAlignedDiskCount: 2 | +| pageReadersDecodeAlignedDiskTime: 0.129 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 68]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 4.187 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 65 | +| [PlanNodeId 53]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 4.172 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 2 | +| CurrentDeviceIndex: 1 | ++-------------------------------------------------------------------------------------------------------------------------+ +``` + +### 3.3 JSON Format + +Using `EXPLAIN ANALYZE (FORMAT JSON)` returns structured information such as plan phase time consumption, fragment instance statistics, and operator statistics. The JSON format is supported since V2.0.11. + +#### 3.3.1 Default JSON Output + +```SQL +EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM t1; +``` + +The top-level JSON structure is as follows: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`planStatistics`|object|Time consumption of query analysis, partition fetching, schema fetching, logical planning, logical optimization, distributed planning, dispatch, and other phases| +|`fragmentInstancesCount`|number|Number of fragment instances for which statistics are returned| +|`fragmentInstances`|array|Execution statistics of each fragment instance| + +The main fields of `planStatistics` are as follows: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`analyzeCostMs`|planStatistics|SQL analysis time, in ms| +|`fetchPartitionCostMs`|planStatistics|Time to fetch partition information, in ms| +|`fetchSchemaCostMs`|planStatistics|Time to fetch schema information, in ms| +|`logicalPlanCostMs`|planStatistics|Logical plan generation time, in ms| +|`logicalOptimizationCostMs`|planStatistics|Logical optimization time, in ms| +|`distributionPlanCostMs`|planStatistics|Distributed plan generation time, in ms| +|`dispatchCostMs`|planStatistics|Fragment dispatch time, in ms| + +The main fields of `fragmentInstances[]` are as follows: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`id`|fragmentInstances[]|fragment instance id| +|`ip`|fragmentInstances[]|IP of the node that executed this fragment instance| +|`dataRegion`|fragmentInstances[]|Corresponding DataRegion| +|`state`|fragmentInstances[]|Final state of the fragment instance| +|`totalWallTimeMs`|fragmentInstances[]|Total wall time, in ms| +|`initDataQuerySourceCostMs`|fragmentInstances[]|Data source initialization time, in ms| +|`initDataQuerySourceRetryCount`|fragmentInstances[]|Number of data source initialization retries. Only output when greater than 0| +|`seqFileUnclosed`, `seqFileClosed`, `unseqFileUnclosed`, `unseqFileClosed`|fragmentInstances[]|Number of sequential and out-of-order files involved| +|`readyQueuedTimeMs`, `blockQueuedTimeMs`|fragmentInstances[]|Ready / block queue waiting time, in ms| +|`queryStatistics`|fragmentInstances[]|Query statistics such as scanning, filtering, chunk/page reader| +|`operators`|fragmentInstances[]|Operator tree and execution statistics of each operator| + +`operators` is a tree structure corresponding to the plan tree. The main fields are as follows: + +|Field|Type/Occurrence|Description| +|---|---|---| +|`planNodeId`|operators|Corresponding plan node id| +|`nodeType`|operators|plan node type| +|`operatorType`|operators|Actual executed operator type| +|`count`|operators|Operator statistics aggregation count. Only output when present| +|`cpuTimeMs`|operators|Operator CPU execution time, in ms| +|`outputRows`|operators|Number of output rows| +|`hasNextCalledCount`, `nextCalledCount`|operators|Number of operator iteration calls| +|`estimatedMemorySize`|operators|Estimated memory usage. Only output when non-zero| +|`specifiedInfo`|operators|Operator-specific statistics. Only output when non-empty| +|`children`|operators|Child operator array| + +Taking `table1` in the [sample data](../Reference/Sample-Data.md) as an example: + +```SQL +EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 4.948, + "fetchPartitionCostMs": 2.104, + "fetchSchemaCostMs": 13.321, + "logicalPlanCostMs": 2.264, + "logicalOptimizationCostMs": 7.941, + "distributionPlanCostMs": 1.434, + "dispatchCostMs": 9.023 + }, + "fragmentInstancesCount": 3, + "fragmentInstances": [ + { + "id": "20260722_064302_00101_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "virtual_data_region", + "state": "FINISHED", + "totalWallTimeMs": 97, + "initDataQuerySourceCostMs": 0.0, + "seqFileUnclosed": 0, + "seqFileClosed": 0, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.858, + "blockQueuedTimeMs": 71.752, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "69", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 6.082, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "DownStreamPlanNodeId": "66" + }, + "children": [ + { + "planNodeId": "55", + "nodeType": "CollectNode", + "operatorType": "CollectOperator", + "cpuTimeMs": 6.065, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "children": [ + { + "planNodeId": "64", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 1.119, + "outputRows": 12, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "3264" + } + }, + { + "planNodeId": "65", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 4.909, + "outputRows": 6, + "hasNextCalledCount": 3, + "nextCalledCount": 2, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "1632" + } + } + ] + } + ] + } + }, + { + "id": "20260722_064302_00101_1.3.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 63, + "initDataQuerySourceCostMs": 0.338, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.273, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "67", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 57.338, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "64" + }, + "children": [ + { + "planNodeId": "54", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 57.248, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "4", + "CurrentDeviceIndex": "3" + } + } + ] + } + }, + { + "id": "20260722_064302_00101_1.4.0", + "ip": "127.0.0.1:10730", + "dataRegion": "2", + "state": "FINISHED", + "totalWallTimeMs": 79, + "initDataQuerySourceCostMs": 0.231, + "seqFileUnclosed": 0, + "seqFileClosed": 1, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.038, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "68", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 66.387, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "65" + }, + "children": [ + { + "planNodeId": "53", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 66.362, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "2", + "CurrentDeviceIndex": "1" + } + } + ] + } + } + ] +} ++---------------+ +``` + +#### 3.3.2 VERBOSE JSON Output + +When using `EXPLAIN ANALYZE VERBOSE (FORMAT JSON)`, the top-level structure is the same as the non-verbose JSON, but `queryStatistics` supplements finer-grained statistics beyond the basic filtered row counts, including bloom filter, time series metadata, chunk, page reader, modification, and actual IO size information. + +The main differences between `VERBOSE` and non-`VERBOSE` JSON output are as follows: + +|Output Mode|Top-level Structure|`queryStatistics` Field Granularity|Typical Fields| +|---|---|---|---| +|`EXPLAIN ANALYZE (FORMAT JSON)`|`planStatistics`, `fragmentInstancesCount`, `fragmentInstances`|Default statistics fields|`timeSeriesIndexFilteredRows`, `chunkIndexFilteredRows`, `pageIndexFilteredRows`| +|`EXPLAIN ANALYZE VERBOSE (FORMAT JSON)`|Same as non-`VERBOSE`|More detailed scanning, reading, and filtering statistics|`loadBloomFilterFromCacheCount`, `loadTimeSeriesMetadataFromCacheCount`, `loadChunkFromDiskCount`, `pageReadersDecodeAlignedMemCount`, `rowScanFilteredRows`| + +Taking `table1` in the [sample data](../Reference/Sample-Data.md) as an example: + +```SQL +EXPLAIN ANALYZE VERBOSE (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 3.505, + "fetchPartitionCostMs": 2.003, + "fetchSchemaCostMs": 9.198, + "logicalPlanCostMs": 1.481, + "logicalOptimizationCostMs": 6.684, + "distributionPlanCostMs": 0.711, + "dispatchCostMs": 8.32 + }, + "fragmentInstancesCount": 3, + "fragmentInstances": [ + { + "id": "20260722_064502_00103_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "virtual_data_region", + "state": "FINISHED", + "totalWallTimeMs": 48, + "initDataQuerySourceCostMs": 0.0, + "seqFileUnclosed": 0, + "seqFileClosed": 0, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.209, + "blockQueuedTimeMs": 17.997, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 0, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.0, + "loadTimeSeriesMetadataFromCacheCount": 0, + "loadTimeSeriesMetadataFromDiskCount": 0, + "loadTimeSeriesMetadataActualIOSize": 0, + "loadChunkFromCacheCount": 0, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "69", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 26.368, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "DownStreamPlanNodeId": "66" + }, + "children": [ + { + "planNodeId": "55", + "nodeType": "CollectNode", + "operatorType": "CollectOperator", + "cpuTimeMs": 26.32, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "children": [ + { + "planNodeId": "64", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 0.47, + "outputRows": 12, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "3264" + } + }, + { + "planNodeId": "65", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 25.802, + "outputRows": 6, + "hasNextCalledCount": 3, + "nextCalledCount": 2, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "1632" + } + } + ] + } + ] + } + }, + { + "id": "20260722_064502_00103_1.3.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 21, + "initDataQuerySourceCostMs": 0.232, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.007, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 2, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.02, + "loadTimeSeriesMetadataAlignedDiskSeqCount": 4, + "loadTimeSeriesMetadataAlignedDiskSeqTimeMs": 0.483, + "loadTimeSeriesMetadataFromCacheCount": 18, + "loadTimeSeriesMetadataFromDiskCount": 2, + "loadTimeSeriesMetadataActualIOSize": 0, + "alignedTimeSeriesMetadataModificationTimeMs": 0.061, + "constructAlignedChunkReadersDiskCount": 4, + "constructAlignedChunkReadersDiskTimeMs": 2.693, + "loadChunkFromCacheCount": 18, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "pageReadersDecodeAlignedDiskCount": 4, + "pageReadersDecodeAlignedDiskTimeMs": 0.22, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "67", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 5.002, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "64" + }, + "children": [ + { + "planNodeId": "54", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 4.94, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "4", + "CurrentDeviceIndex": "3" + } + } + ] + } + }, + { + "id": "20260722_064502_00103_1.4.0", + "ip": "127.0.0.1:10730", + "dataRegion": "2", + "state": "FINISHED", + "totalWallTimeMs": 32, + "initDataQuerySourceCostMs": 0.253, + "seqFileUnclosed": 0, + "seqFileClosed": 1, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.027, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 0, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.0, + "loadTimeSeriesMetadataAlignedDiskSeqCount": 2, + "loadTimeSeriesMetadataAlignedDiskSeqTimeMs": 0.188, + "loadTimeSeriesMetadataFromCacheCount": 10, + "loadTimeSeriesMetadataFromDiskCount": 0, + "loadTimeSeriesMetadataActualIOSize": 0, + "alignedTimeSeriesMetadataModificationTimeMs": 0.037, + "constructAlignedChunkReadersDiskCount": 2, + "constructAlignedChunkReadersDiskTimeMs": 1.807, + "loadChunkFromCacheCount": 10, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "pageReadersDecodeAlignedDiskCount": 2, + "pageReadersDecodeAlignedDiskTimeMs": 0.62, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "68", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 3.652, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "65" + }, + "children": [ + { + "planNodeId": "53", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 3.633, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "2", + "CurrentDeviceIndex": "1" + } + } + ] + } + } + ] +} +``` + +## 4. Scenario Examples + +### 4.1 CTE Query Plan + +When `EXPLAIN (FORMAT JSON)` analyzes a query containing a materialized CTE, the output uses the wrapped structure of `cteQueries` + `mainQuery`. + +Taking `table1` and `table2` in the [sample data](../Reference/Sample-Data.md) as an example: + +```SQL +EXPLAIN (FORMAT JSON) +WITH cte1 AS MATERIALIZED (SELECT * FROM table2) +SELECT * FROM table1 +WHERE table1.device_id IN (SELECT device_id FROM cte1); +``` + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "cteQueries": [ + { + "name": "cte1", + "plan": { + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "CollectNode-33", + "id": "33", + "children": [ + { + "name": "ExchangeNode-40", + "id": "40", + "children": [ + { + "name": "DeviceTableScanNode-32", + "id": "32", + "properties": { + "QualifiedTableName": "database1.table2", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-41", + "id": "41", + "children": [ + { + "name": "DeviceTableScanNode-31", + "id": "31", + "properties": { + "QualifiedTableName": "database1.table2", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] + } + } + ], + "mainQuery": { + "name": "OutputNode-12", + "id": "12", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "ProjectNode-35", + "id": "35", + "properties": { + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "Expressions": [ + "\"time\"", + "\"region\"", + "\"plant_id\"", + "\"device_id\"", + "\"model_id\"", + "\"maintenance\"", + "\"temperature\"", + "\"humidity\"", + "\"status\"", + "\"arrival_time\"" + ] + }, + "children": [ + { + "name": "FilterNode-138", + "id": "138", + "properties": { + "Predicate": "\"expr\"" + }, + "children": [ + { + "name": "SemiJoinNode-109", + "id": "109", + "children": [ + { + "name": "ExchangeNode-218", + "id": "218", + "children": [ + { + "name": "MergeSortNode-197", + "id": "197", + "properties": { + "OrderBy": "{orderBy\u003d[device_id], orderings\u003d{device_id\u003dASC NULLS LAST}}" + }, + "children": [ + { + "name": "ExchangeNode-216", + "id": "216", + "children": [ + { + "name": "DeviceTableScanNode-196", + "id": "196", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "true", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-217", + "id": "217", + "children": [ + { + "name": "DeviceTableScanNode-195", + "id": "195", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "true", + "RegionId": "2" + } + } + ] + } + ] + } + ] + }, + { + "name": "ExchangeNode-219", + "id": "219", + "children": [ + { + "name": "SortNode-137", + "id": "137", + "properties": { + "OrderBy": "{orderBy\u003d[device_id_3], orderings\u003d{device_id_3\u003dASC NULLS FIRST}}" + }, + "children": [ + { + "name": "ProjectNode-6", + "id": "6", + "properties": { + "OutputSymbols": [ + "device_id_3" + ], + "Expressions": [ + "\"device_id_3\"" + ] + }, + "children": [ + { + "name": "CteScanNode-2", + "id": "2" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } +} ++-----------------+ +``` + +### 4.2 Prepared Statement and Dynamic SQL + +When analyzing prepared statements and dynamic SQL via `EXECUTE` or `EXECUTE IMMEDIATE`, the specified output format continues to take effect after the statement is expanded, and will not fall back to the default `GRAPHVIZ` or `TEXT` format. + +```SQL +PREPARE explain_json_stmt FROM SELECT * FROM table1 WHERE device_id = ?; +EXPLAIN (FORMAT JSON) EXECUTE explain_json_stmt USING '101'; + +EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE 'SELECT * FROM table1 WHERE device_id = ?' USING '101'; +``` + +Where: + +- `EXPLAIN (FORMAT JSON) EXECUTE ...` returns a plan node JSON object. + +- `EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE ...` returns a JSON object containing `planStatistics`, `fragmentInstancesCount`, and `fragmentInstances`. + +1. `EXPLAIN (FORMAT JSON) EXECUTE ...` output structure example: + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-5", + "id": "5", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "DeviceTableScanNode-46", + "id": "46", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] +} ++-----------------+ +``` + +2. `EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE ...` output structure example: + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 2.904, + "fetchPartitionCostMs": 1.35, + "fetchSchemaCostMs": 5.259, + "logicalPlanCostMs": 1.123, + "logicalOptimizationCostMs": 6.157, + "distributionPlanCostMs": 0.571, + "dispatchCostMs": 5.909 + }, + "fragmentInstancesCount": 1, + "fragmentInstances": [ + { + "id": "20260722_065558_00118_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 10, + "initDataQuerySourceCostMs": 1.768, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.059, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "74", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 9.193, + "outputRows": 10, + "hasNextCalledCount": 7, + "nextCalledCount": 6, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "73" + }, + "children": [ + { + "planNodeId": "68", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 9.055, + "outputRows": 10, + "hasNextCalledCount": 7, + "nextCalledCount": 6, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "3", + "CurrentDeviceIndex": "2" + } + } + ] + } + } + ] +} ++---------------+ +``` + +### 4.3 JSON Display in CLI + +The CLI outputs regular query results in tabular format by default. For JSON-format query analysis results, if the JSON body continues to be placed in table cells, each row would carry `|` borders, which is not conducive to copying, saving, and JSON parsing. + +The CLI switches to raw JSON display when the following conditions are met: + +|Condition|Description| +|---|---| +|The result set has only one column|Avoids affecting normal multi-column query display| +|The column name is `distribution plan` or `Explain Analyze`|Only matches the result columns of `EXPLAIN` / `EXPLAIN ANALYZE`| +|The first result value trimmed starts with `{` or `[`|Only enables raw output for JSON object / array content| + +Raw JSON display rules: + +- The CLI still outputs the column name header and outer separator line, so users can identify the current column as `distribution plan` or `Explain Analyze`. + +- The JSON body is output line by line as-is, without adding `|` table borders. + +- After the JSON ends, a separator line and row count statistics are output. + +- Non-JSON `EXPLAIN`, default text `EXPLAIN ANALYZE`, and regular query results still use the original table output. + +The differences between raw JSON output and regular table output are as follows: + +|Output Type|Applicable Result|Display Method|Copy and Parse| +|---|---|---|---| +|Regular table output|Regular query results, default `EXPLAIN`, default `EXPLAIN ANALYZE`|Each row's content is placed inside table borders|Suitable for human viewing, not suitable for direct JSON copy parsing| +|Raw JSON output|`EXPLAIN (FORMAT JSON)`, `EXPLAIN ANALYZE (FORMAT JSON)`|Retains column name header, JSON body has no table borders|Suitable for direct copying, saving, or handing to a JSON parser| + +Example: + +```SQL +start-cli.sh -sql_dialect table -e "EXPLAIN (FORMAT JSON) SELECT time, device_id, temperature FROM database1.table1" +``` + +```SQL ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "device_id", + "temperature" + ], + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ] + }, + "children": [ + { + "name": "CollectNode-42", + "id": "42", + "children": [ + { + "name": "ExchangeNode-49", + "id": "49", + "children": [ + { + "name": "DeviceTableScanNode-41", + "id": "41", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-50", + "id": "50", + "children": [ + { + "name": "DeviceTableScanNode-40", + "id": "40", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] +} ++-----------------+ +``` + +### 4.4 Illegal Format Combinations + +`EXPLAIN` and `EXPLAIN ANALYZE` support different formats. Illegal format combinations return a clear error. + +|Statement|Default Format|Supported Formats|Unsupported Formats| +|---|---|---|---| +|`EXPLAIN`|`GRAPHVIZ`|`GRAPHVIZ`, `JSON`|`TEXT`, `XML`, other unknown formats| +|`EXPLAIN ANALYZE`|`TEXT`|`TEXT`, `JSON`|`GRAPHVIZ`, `XML`, other unknown formats| + +Example: + +```SQL +EXPLAIN (FORMAT TEXT) SELECT * FROM table1; +EXPLAIN ANALYZE (FORMAT GRAPHVIZ) SELECT * FROM table1; +EXPLAIN (FORMAT XML) SELECT * FROM table1; +``` + +Corresponding errors: + +```SQL +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN format: TEXT. Supported formats: GRAPHVIZ, JSON +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN ANALYZE format: GRAPHVIZ. Supported formats: TEXT, JSON +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN format: XML. Supported formats: GRAPHVIZ, JSON +``` + +## 5. FAQ + +### 5.1 What to do when a query times out? + +`EXPLAIN ANALYZE` actually executes the query, so it may also fail to return complete results when the query times out. To assist troubleshooting, IoTDB writes the current analysis results to a dedicated log in text form at certain time intervals; the log interval is calculated based on the query timeout, ensuring at least two result records before timeout. + +When encountering a timeout, handle it in the following order: + +1. Check the staged analysis results for the corresponding query in `logs/log_explain_analyze.log`. + +2. If there are no results in the log, check whether only the lib package was replaced during the upgrade, and `conf/logback-datanode.xml` was not replaced synchronously. + +3. After replacing the configuration, no restart is required. Wait for the configuration hot reload to complete, then execute `EXPLAIN ANALYZE VERBOSE`. + +### 5.2 What is the difference between WALL TIME and CPU TIME? + +CPU time refers to the time the program actually occupies the CPU for computation during execution, representing the processor resources actually consumed by the program. + +Wall time refers to the real physical time from when the program starts execution to when it ends, including resource waiting time. + +`WALL TIME < CPU TIME` is common in parallel execution scenarios. For example, if a query fragment is executed in parallel by two threads, 10 seconds of physical time pass, but the two threads each occupy one CPU core running for 10 seconds, then the CPU TIME is about 20 seconds, and the WALL TIME is about 10 seconds. + +`WALL TIME > CPU TIME` is common in resource waiting scenarios. For example, a query fragment enters the blocked queue due to insufficient memory or waiting for upstream data, or enters the ready queue due to insufficient query thread resources. During the wait, no CPU is occupied, but physical time still passes. + +### 5.3 Does EXPLAIN ANALYZE have additional overhead? + +The statistics collection itself has almost no significant additional overhead. The `EXPLAIN ANALYZE` operator collects the statistics already available from the original query, and traverses via `next` without printing the query results, so there is usually no significant difference in time consumption compared to directly executing the same query. + +Note that the analyzed query is still fully executed and normally consumes CPU, memory, and IO resources. When evaluating the impact on the production environment, refer to the resource consumption of the original query itself. + +### 5.4 Which metrics should I focus on for IO time consumption? + +Metrics involving IO time consumption mainly include `loadBloomFilterActualIOSize`, `loadBloomFilterTime`, `loadTimeSeriesMetadataAlignedDiskSeqTime`, `loadTimeSeriesMetadataAlignedDiskUnseqTime`, `loadTimeSeriesMetadataActualIOSize`, `alignedTimeSeriesMetadataModificationTime`, `constructAlignedChunkReadersDiskTime`, and `loadChunkActualIOSize`. + +TimeSeriesMetadata loading statistics are separated for sequential and out-of-order files, but Chunk reading is not yet separately counted for sequential and out-of-order proportions; this can be estimated through the sequential/out-of-order ratio of TimeSeriesMetadata. + +### 5.5 How to observe the impact of out-of-order data on query performance? + +Out-of-order data mainly produces two types of impact: + +1. During query, an additional merge sort needs to be done in memory. + +2. Out-of-order data produces time range overlaps between data blocks, causing some statistics to be unusable for directly skipping data blocks that do not meet conditions, or for directly calculating aggregate values. + +Currently, there is no direct observation metric specifically for the impact of out-of-order data. Usually, you can execute a query once when out-of-order data exists, and then execute the query again after the out-of-order data is merged, and evaluate by comparing the time consumption before and after. diff --git a/src/zh/UserGuide/Master/Table/SQL-Manual/SQL-Maintenance-Statements_apache.md b/src/zh/UserGuide/Master/Table/SQL-Manual/SQL-Maintenance-Statements_apache.md index c38314a40..630493e2d 100644 --- a/src/zh/UserGuide/Master/Table/SQL-Manual/SQL-Maintenance-Statements_apache.md +++ b/src/zh/UserGuide/Master/Table/SQL-Manual/SQL-Maintenance-Statements_apache.md @@ -493,7 +493,7 @@ IoTDB> KILL ALL QUERIES; -- 终止所有query EXPLAIN ``` -更多详细语法说明请参考:[EXPLAIN 语句](../User-Manual/Query-Performance-Analysis.md#_1-explain-语句) +更多详细语法说明请参考:[EXPLAIN 语句](../User-Manual/Query-Performance-Analysis_apache.md#_2-explain) **示例:** @@ -542,7 +542,7 @@ IoTDB> explain select * from t1 EXPLAIN ANALYZE [VERBOSE] ``` -更多详细语法说明请参考:[EXPLAIN ANALYZE 语句](../User-Manual/Query-Performance-Analysis.md#_2-explain-analyze-语句) +更多详细语法说明请参考:[EXPLAIN ANALYZE 语句](../User-Manual/Query-Performance-Analysis_apache.md#_3-explain-analyze) **示例:** diff --git a/src/zh/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis.md b/src/zh/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis.md deleted file mode 100644 index 43d24f16b..000000000 --- a/src/zh/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis.md +++ /dev/null @@ -1,341 +0,0 @@ - -# 查询性能分析 - -查询分析的意义在于帮助用户理解查询的执行机制和性能瓶颈,从而实现查询优化和性能提升。这不仅关乎到查询的执行效率,也直接影响到应用的用户体验和资源的有效利用。为了进行有效的查询分析,IoTDB 提供了查询分析语句:Explain 和 Explain Analyze。 - -- Explain 语句:允许用户预览查询 SQL 的执行计划,包括 IoTDB 如何组织数据检索和处理。 -- Explain Analyze 语句:在 Explain 语句基础上增加了性能分析,完整执行 SQL 并展示查询执行过程中的时间和资源消耗。为 IoTDB 用户深入理解查询详情以及进行查询优化提供了详细的相关信息。与其他常用的 IoTDB 排查手段相比,Explain Analyze 没有部署负担,同时能够针对单条 sql 进行分析,能够更好定位问题。各类方法对比如下:: - -| 方法 | 安装难度 | 业务影响 | 功能范围 | -| ------------------- | ------------------------------------------------------------ | ---------------------------------------------------- | ------------------------------------------------------ | -| Explain Analyze语句 | 低。无需安装额外组件,为IoTDB内置SQL语句 | 低。只会影响当前分析的单条查询,对线上其他负载无影响 | 支持分布式,可支持对单条SQL进行追踪 | -| 监控面板 | 中。需要安装IoTDB监控面板工具,并开启IoTDB监控服务 | 中。IoTDB监控服务记录指标会带来额外耗时 | 支持分布式,仅支持对数据库整体查询负载和耗时进行分析 | -| Arthas抽样 | 中。需要安装Java Arthas工具(部分内网无法直接安装Arthas,且安装后,有时需要重启应用) | 高。CPU 抽样可能会影响线上业务的响应速度 | 不支持分布式,仅支持对数据库整体查询负载和耗时进行分析 | - -## 1. Explain 语句 - -#### 语法 - -Explain命令允许用户查看SQL查询的执行计划。执行计划以算子的形式展示,描述了IoTDB会如何执行查询。其语法如下,其中SELECT_STATEMENT是查询相关的SQL语句: - -```SQL -EXPLAIN -``` -#### 含义 - -```SQL --- 创建数据库 -create database test; - --- 创建表 -use test; -create table t1 (device_id STRING ID, type STRING ATTRIBUTE, speed float); - --- 插入数据 -insert into t1(device_id, type, speed) values('car_1', 'Model Y', 120.0); -insert into t1(device_id, type, speed) values('car_2', 'Model 3', 100.0); - --- 执行 explain 语句 -explain select * from t1; -``` -执行上方SQL,会得到如下结果。不难看出,IoTDB 分别通过两个 TableScan 节点去不同的数据分区中获取表中的数据,最后通过Collect 算子汇总数据后返回。 - -```SQL -+-----------------------------------------------------------------------------------------------+ -| distribution plan| -+-----------------------------------------------------------------------------------------------+ -| ┌─────────────────────────────────────────────┐ | -| │OutputNode-4 │ | -| │OutputColumns-[time, device_id, type, speed] │ | -| │OutputSymbols: [time, device_id, type, speed]│ | -| └─────────────────────────────────────────────┘ | -| │ | -| │ | -| ┌─────────────────────────────────────────────┐ | -| │Collect-21 │ | -| │OutputSymbols: [time, device_id, type, speed]│ | -| └─────────────────────────────────────────────┘ | -| ┌───────────────────────┴───────────────────────┐ | -| │ │ | -|┌─────────────────────────────────────────────┐ ┌───────────┐ | -|│TableScan-19 │ │Exchange-28│ | -|│QualifiedTableName: test.t1 │ └───────────┘ | -|│OutputSymbols: [time, device_id, type, speed]│ │ | -|│DeviceNumber: 1 │ │ | -|│ScanOrder: ASC │ ┌─────────────────────────────────────────────┐| -|│PushDownOffset: 0 │ │TableScan-20 │| -|│PushDownLimit: 0 │ │QualifiedTableName: test.t1 │| -|│PushDownLimitToEachDevice: false │ │OutputSymbols: [time, device_id, type, speed]│| -|│RegionId: 2 │ │DeviceNumber: 1 │| -|└─────────────────────────────────────────────┘ │ScanOrder: ASC │| -| │PushDownOffset: 0 │| -| │PushDownLimit: 0 │| -| │PushDownLimitToEachDevice: false │| -| │RegionId: 1 │| -| └─────────────────────────────────────────────┘| -+-----------------------------------------------------------------------------------------------+ -``` - - -## 2. Explain Analyze 语句 - -#### 语法 - -```SQL -EXPLAIN ANALYZE [VERBOSE] -``` - -其中 SELECT_STATEMENT 对应需要分析的查询语句;VERBOSE为打印详细分析结果,不填写VERBOSE时EXPLAIN ANALYZE将会省略部分信息。 - -#### 含义 - -Explain Analyze 是 IOTDB 查询引擎自带的性能分析 SQL,与 Explain 不同,它会执行对应的查询计划并统计执行信息,可以用于追踪一条查询的具体性能分布,用于对资源进行观察,进行性能调优与异常分析。 - -在 EXPLAIN ANALYZE 的结果集中,会包含如下信息: - -![explain-analyze-1.png](/img/explain-analyze-1.png) - -其中: - -- QueryStatistics 包含查询层面进的统计信息,主要包含各规划阶段耗时,查询分片数量等信息。 - - Analyze Cost: SQL 分析阶段的耗时(包含 FetchPartitionCost 和 FetchSchemaCost) - - Fetch Partition Cost:拉取分区表的耗时 - - Fetch Schema Cost:拉取元数据以及权限校验的耗时 - - Logical Plan Cost:构建逻辑计划的耗时 - - Logical Optimization Cost: 逻辑计划优化的耗时 - - Distribution Plan Cost:构建分布式计划的耗时 - - Fragment Instance Count:总的查询分片的数量,每个查询分片的信息会挨个输出 -- FragmentInstance 是 IoTDB 一个查询分片的封装,每一个查询分片都会在结果集中输出一份分片的执行信息,主要包含 FragmentStatistics 和算子信息。FragmentStastistics 包含 Fragment 的统计信息,包括总实际耗时(墙上时间),所涉及到的 TsFile,调度信息等情况。在一个 Fragment 的信息输出同时会以节点树层级的方式展示该Fragment 下计划节点的统计信息,主要包括:CPU运行时间、输出的数据行数、指定接口被调用的次数、所占用的内存、节点专属的定制信息。 - - Total Wall Time: 该分片从开始执行到执行结束的物理时间 - - Cost of initDataQuerySource:构建查询文件列表 - - Seq File(unclosed): 未封口(memtable)的顺序文件数量, Seq File(closed): 封口的顺序文件数量 - - UnSeq File(unclosed): 未封口(memtable)的乱序文件数量, UnSeq File(closed): 未封口的乱序文件数量 - - ready queued time: 查询分片的所有task在 ready queue 中的总时长(task 未阻塞,但没有查询执行线程资源时,会被放入ready queue),blocked queued time: 查询分片的所有task在 bloced queue 中的总时长(task 因为某些资源,如内存,或者上游数据未发送过来,则会被放入blocked queue) - - BloomFilter 相关(用于判断某个序列在tsfile中是否存在,存储于tsfile尾部) - - loadBloomFilterFromCacheCount: 命中BloomFilterCache的次数 - - loadBloomFilterFromDiskCount: 从磁盘中读取的次数 - - loadBloomFilterActualIOSize: 从磁盘中读取BloomFilter时耗费的磁盘IO(单位为bytes) - - loadBloomFilterTime: 读取BloomFilter + 计算序列是否存在的总耗时(单位为ms) - - TimeSeriesMetadata 相关(序列在tsfile中的索引信息,一个序列在一个tsfile中只会存储一个) - - loadTimeSeriesMetadataDiskSeqCount: 从封口的顺序文件里加载出的TimeSeriesMetadata数量 - - 大部分情况下等于Seq File(closed),但如果有limit等算子,可能实际加载的数量会小于Seq File(closed) - - loadTimeSeriesMetadataDiskUnSeqCount: 从封口的乱序文件里加载出的TimeSeriesMetadata数量 - - 大部分情况下等于UnSeq File(closed),但如果有limit等算子,可能实际加载的数量会小于UnSeq File(closed) - - loadTimeSeriesMetadataDiskSeqTime: 从封口的顺序文件里加载TimeSeriesMetadata的耗时 - - 并不是所有的 TimeSeriesMetadata 加载都涉及磁盘 IO,有可能会命中TimeSeriesMetadataCache,直接从缓存中读取,但输出信息里并没有分开统计这两者的耗时 - - loadTimeSeriesMetadataDiskUnSeqTime: 从未封口的顺序文件里加载TimeSeriesMetadata的耗时 - - loadTimeSeriesMetadataFromCacheCount: 命中TimeSeriesMetadataCache的次数,注意这里对于对齐设备来讲,每个分量(包括time列,都会去单独请求Cache,所以对于对齐设备来讲,(loadTimeSeriesMetadataFromCacheCount + loadTimeSeriesMetadataFromDiskCount)= tsfile number * subSensor number(包括time列) - - loadTimeSeriesMetadataFromDiskCount: 从磁盘中读取TimeSeriesMetadata的次数,一次读取会把该设备下查询所有涉及到的分量都读出来缓存在TimeSeriesMetadataCache中 - - loadTimeSeriesMetadataActualIOSize: 从磁盘中读取TimeSeriesMetadata时耗费的磁盘IO(单位为bytes) - - Mods 文件相关 - - TimeSeriesMetadataModificationTime: 读取mods文件花费的时间 - - Chunk 相关 - - constructAlignedChunkReadersDiskCount: 读取已封口 tsfile 中总的 Chunk 数量 - - constructAlignedChunkReadersDiskTime: 读取已封口 tsfile 中 Chunk 的总耗时(包含磁盘IO和解压缩) - - pageReadersDecodeAlignedDiskCount: 解编码已封口 tsfile 中总的 page 数量 - - pageReadersDecodeAlignedDiskTime: 解编码已封口 tsfile 中 page 的总耗时 - - loadChunkFromCacheCount: 命中ChunkCache的次数,注意这里对于对齐设备来讲,每个分量(包括time列,都会去单独请求Cache,所以对于对齐设备来讲,(loadChunkFromCacheCount + loadChunkFromDiskCount)= tsfile number * subSensor number(包括time列)* avg chunk number in each tsfile - - loadChunkFromDiskCount: 从磁盘中读取Chunk的次数,一次只会读取一个分量,并缓存在ChunkCache中 - - loadChunkActualIOSize: 从磁盘中读取Chunk时耗费的磁盘IO(单位为bytes) - - 自V2.0.9起,在 FragmentInstance 中,将增加如下信息: - - OutputPlanNodeId:表示sink节点对应的下游接收数据的节点,仅在sink节点中出现 - - sizeInBytes:表示exchange节点中接收到的TsBlock的字节(仅计算数据占用大小),仅在exchange节点中出现 - - tableScan节点过滤数据相关字段,仅在filter下推到tableScan中才有效,且仅在tableScan节点中出现: - - TimeSeriesIndexFilteredRows : 通过序列元数据(TimeseriesMetadata)过滤掉的数据行数 - - ChunkIndexFilteredRows: 通过列块元数据(ChunkMetadata)过滤掉的数据行数 - - PageIndexFilteredRows:通过页头内部(PageHeader)过滤掉的数据行数 - - RowScanFilteredRows:一行行检查数据时候被过滤掉的行数(仅在带有verbose时才会展现) - -##### 特别说明 - -查询超时场景使用 Explain Analyze 语句: - -Explain Analyze 本身是一种特殊的查询,所以当执行超时的时候,Explain Analyze 语句也无法正常返回结果。为了在查询超时的情况下也可以通过分析结果排查超时原因,Explain Analyze 提供了定时日志机制(无需用户配置),每经过一定的时间间隔会将 Explain Analyze 的当前结果以文本的形式输出到专门的日志中。当查询超时时,用户可以前往logs/log_explain_analyze.log中查看对应的日志进行排查。 -日志的时间间隔基于查询的超时时间进行计算,可以保证在超时的情况下至少会有两次的结果记录。 - -##### 示例 - -下面是Explain Analyze的一个例子: - -```SQL --- 创建数据库 -create database test; - --- 创建表 -use test; -create table t1 (device_id STRING ID, type STRING ATTRIBUTE, speed float); - --- 插入数据 -insert into t1(device_id, type, speed) values('car_1', 'Model Y', 120.0); -insert into t1(device_id, type, speed) values('car_2', 'Model 3', 100.0); - --- 执行 explain analyze 语句 -explain analyze verbose select * from t1; -``` - -得到输出如下: - -```SQL -+-----------------------------------------------------------------------------------------------+ -| Explain Analyze| -+-----------------------------------------------------------------------------------------------+ -|Analyze Cost: 38.860 ms | -|Fetch Partition Cost: 9.888 ms | -|Fetch Schema Cost: 54.046 ms | -|Logical Plan Cost: 10.102 ms | -|Logical Optimization Cost: 17.396 ms | -|Distribution Plan Cost: 2.508 ms | -|Dispatch Cost: 22.126 ms | -|Fragment Instances Count: 2 | -| | -|FRAGMENT-INSTANCE[Id: 20241127_090849_00009_1.2.0][IP: 0.0.0.0][DataRegion: 2][State: FINISHED]| -| Total Wall Time: 18 ms | -| Cost of initDataQuerySource: 6.153 ms | -| Seq File(unclosed): 1, Seq File(closed): 0 | -| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | -| ready queued time: 0.164 ms, blocked queued time: 0.342 ms | -| Query Statistics: | -| loadBloomFilterFromCacheCount: 0 | -| loadBloomFilterFromDiskCount: 0 | -| loadBloomFilterActualIOSize: 0 | -| loadBloomFilterTime: 0.000 | -| loadTimeSeriesMetadataAlignedMemSeqCount: 1 | -| loadTimeSeriesMetadataAlignedMemSeqTime: 0.246 | -| loadTimeSeriesMetadataFromCacheCount: 0 | -| loadTimeSeriesMetadataFromDiskCount: 0 | -| loadTimeSeriesMetadataActualIOSize: 0 | -| constructAlignedChunkReadersMemCount: 1 | -| constructAlignedChunkReadersMemTime: 0.294 | -| loadChunkFromCacheCount: 0 | -| loadChunkFromDiskCount: 0 | -| loadChunkActualIOSize: 0 | -| pageReadersDecodeAlignedMemCount: 1 | -| pageReadersDecodeAlignedMemTime: 0.047 | -| [PlanNodeId 43]: IdentitySinkNode(IdentitySinkOperator) | -| CPU Time: 5.523 ms | -| output: 2 rows | -| HasNext() Called Count: 6 | -| Next() Called Count: 5 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 31]: CollectNode(CollectOperator) | -| CPU Time: 5.512 ms | -| output: 2 rows | -| HasNext() Called Count: 6 | -| Next() Called Count: 5 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 29]: TableScanNode(TableScanOperator) | -| CPU Time: 5.439 ms | -| output: 1 rows | -| HasNext() Called Count: 3 -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| DeviceNumber: 1 | -| CurrentDeviceIndex: 0 | -| [PlanNodeId 40]: ExchangeNode(ExchangeOperator) | -| CPU Time: 0.053 ms | -| output: 1 rows | -| HasNext() Called Count: 2 | -| Next() Called Count: 1 | -| Estimated Memory Size: : 131072 | -| | -|FRAGMENT-INSTANCE[Id: 20241127_090849_00009_1.3.0][IP: 0.0.0.0][DataRegion: 1][State: FINISHED]| -| Total Wall Time: 13 ms | -| Cost of initDataQuerySource: 5.725 ms | -| Seq File(unclosed): 1, Seq File(closed): 0 | -| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | -| ready queued time: 0.118 ms, blocked queued time: 5.844 ms | -| Query Statistics: | -| loadBloomFilterFromCacheCount: 0 | -| loadBloomFilterFromDiskCount: 0 | -| loadBloomFilterActualIOSize: 0 | -| loadBloomFilterTime: 0.000 | -| loadTimeSeriesMetadataAlignedMemSeqCount: 1 | -| loadTimeSeriesMetadataAlignedMemSeqTime: 0.004 | -| loadTimeSeriesMetadataFromCacheCount: 0 | -| loadTimeSeriesMetadataFromDiskCount: 0 | -| loadTimeSeriesMetadataActualIOSize: 0 | -| constructAlignedChunkReadersMemCount: 1 | -| constructAlignedChunkReadersMemTime: 0.001 | -| loadChunkFromCacheCount: 0 | -| loadChunkFromDiskCount: 0 | -| loadChunkActualIOSize: 0 | -| pageReadersDecodeAlignedMemCount: 1 | -| pageReadersDecodeAlignedMemTime: 0.007 | -| [PlanNodeId 42]: IdentitySinkNode(IdentitySinkOperator) | -| CPU Time: 0.270 ms | -| output: 1 rows | -| HasNext() Called Count: 3 | -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 30]: TableScanNode(TableScanOperator) | -| CPU Time: 0.250 ms | -| output: 1 rows | -| HasNext() Called Count: 3 | -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| DeviceNumber: 1 | -| CurrentDeviceIndex: 0 | -+-----------------------------------------------------------------------------------------------+ -``` - -## 3. 常见问题 - -#### 1. WALL TIME(墙上时间)和 CPU TIME(CPU时间)的区别? - -CPU 时间也称为处理器时间或处理器使用时间,指的是程序在执行过程中实际占用 CPU 进行计算的时间,显示的是程序实际消耗的处理器资源。 - -墙上时间也称为实际时间或物理时间,指的是从程序开始执行到程序结束的总时间,包括了所有等待时间。。 - -1. WALL TIME < CPU TIME 的场景:比如一个查询分片最后被调度器使用两个线程并行执行,真实物理世界上是 10s 过去了,但两个线程,可能一直占了两个 cpu 核跑了 10s,那 cpu time 就是 20s,wall time就是 10s -2. WALL TIME > CPU TIME 的场景:因为系统内可能会存在多个查询并行执行,但查询的执行线程数和内存是固定的, - 1. 所以当查询分片被某些资源阻塞住时(比如没有足够的内存进行数据传输、或等待上游数据)就会放入Blocked Queue,此时查询分片并不会占用 CPU TIME,但WALL TIME(真实物理时间的时间)是在向前流逝的 - 2. 或者当查询线程资源不足时,比如当前共有16个查询线程,但系统内并发有20个查询分片,即使所有查询都没有被阻塞,也只会同时并行运行16个查询分片,另外四个会被放入 READY QUEUE,等待被调度执行,此时查询分片并不会占用 CPU TIME,但WALL TIME(真实物理时间的时间)是在向前流逝的 - - -#### 2. Explain Analyze 是否有额外开销,测出的耗时是否与查询真实执行时有差别? - -几乎没有,因为 explain analyze operator 是单独的线程执行,收集原查询的统计信息,且这些统计信息,即使不explain analyze,原来的查询也会生成,只是没有人去取。并且 explain analyze 是纯 next 遍历结果集,不会打印,所以与原来查询真实执行时的耗时不会有显著差别。 - -#### 3. IO 耗时主要关注几个指标? - -涉及 IO 耗时的指标主要有:loadBloomFilterActualIOSize, loadBloomFilterTime, loadTimeSeriesMetadataAlignedDisk[Seq/Unseq]Time, loadTimeSeriesMetadataActualIOSize, alignedTimeSeriesMetadataModificationTime, constructAlignedChunkReadersDiskTime, loadChunkActualIOSize,各指标的具体含义见上文。 -TimeSeriesMetadata 的加载分别统计了顺序和乱序文件,但 Chunk 的读取暂时未分开统计,但顺乱序比例可以通过TimeseriesMetadata 顺乱序的比例计算出来。 - - -#### 4. 乱序数据对查询性能的影响能否有一些指标展示出来? - -乱序数据产生的影响主要有两个: - -1. 需要在内存中多做一个归并排序(一般认为这个耗时是比较短的,毕竟是纯内存的 cpu 操作) -2. 乱序数据会产生数据块间的时间范围重叠,导致统计信息无法使用 - 1. 无法利用统计信息直接 skip 掉整个不满足值过滤要求的 chunk - 1. 一般用户的查询都是只包含时间过滤条件,则不会有影响 - 2. 无法利用统计信息直接计算出聚合值,无需读取数据 - -单独对于乱序数据的性能影响,目前并没有有效的观测手段,除非就是在有乱序数据的时候,执行一遍查询耗时,然后等乱序合完了,再执行一遍,才能对比出来。 - -因为即使乱序这部分数据进了顺序,也是需要 IO、加压缩、decode,这个耗时少不了,不会因为乱序数据被合并进乱序了,就减少了。 - -#### 5. 执行 explain analyze 时,查询超时后,为什么结果没有输出在 log_explain_analyze.log 中? - -升级时,只替换了 lib 包,没有替换 conf/logback-datanode.xml,需要替换一下 conf/logback-datanode.xml,然后不需要重启(该文件内容可以被热加载),大约等待 1 分钟后,重新执行 explain analyze verbose。 diff --git a/src/zh/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis_apache.md b/src/zh/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis_apache.md new file mode 100644 index 000000000..f5fe38525 --- /dev/null +++ b/src/zh/UserGuide/Master/Table/User-Manual/Query-Performance-Analysis_apache.md @@ -0,0 +1,1829 @@ + +# 查询性能分析 + +## 1. 概述 + +查询分析用于帮助用户理解查询的执行机制和性能瓶颈,从而进行查询优化和性能调优。IoTDB 表模型提供两类查询分析语句: + +- `EXPLAIN`:预览查询 SQL 的执行计划,展示 IoTDB 如何组织数据检索和处理。 + +- `EXPLAIN ANALYZE`:在 `EXPLAIN` 基础上真实执行查询,并展示查询执行过程中的时间、资源消耗和算子统计信息。 + +`EXPLAIN` 和 `EXPLAIN ANALYZE` 默认输出面向人工阅读,适合在 CLI 中直接查看。对于 Web Console、CI 回归测试、自动化性能诊断系统等需要稳定解析查询计划或执行统计的场景,可以使用 JSON 输出格式获取结构化结果。JSON 格式自 V2.0.11 起支持。 + +### 1.1 查询分析方式对比 + +与 Arthas 抽样等排查手段相比,`EXPLAIN ANALYZE` 无需部署额外组件,可以针对单条 SQL 进行分布式追踪,更适合定位具体查询的性能问题。 + +|方法|安装难度|业务影响|功能范围| +|---|---|---|---| +|`EXPLAIN ANALYZE` 语句|低。无需安装额外组件,为 IoTDB 内置 SQL 语句|低。只会影响当前分析的单条查询,对线上其他负载无影响|支持分布式,可对单条 SQL 进行追踪| +|Arthas 抽样|中。需要安装 Java Arthas 工具|高。CPU 抽样可能会影响线上业务响应速度|不支持分布式,仅支持对数据库整体查询负载和耗时进行分析| + +## 2. EXPLAIN + +### 2.1 语法 + +`EXPLAIN` 命令用于查看 SQL 查询的分布式执行计划。执行计划以算子树的形式展示,描述 IoTDB 将如何执行查询。 + +```SQL +EXPLAIN [(FORMAT { GRAPHVIZ | JSON })] +``` + +其中: + +|参数|说明| +|---|---| +|`SELECT_STATEMENT`|需要分析的查询语句| +|`FORMAT GRAPHVIZ`|以默认图形文本形式输出分布式计划,适合人工阅读| +|`FORMAT JSON`|以 JSON 对象输出分布式计划,适合程序解析(自 V2.0.11 起支持)| + +`FORMAT` 后的格式名大小写不敏感,例如 `FORMAT json` 与 `FORMAT JSON` 等价。 + +### 2.2 默认 GRAPHVIZ 格式 + +`EXPLAIN` 默认格式为 `GRAPHVIZ`,执行后将得到 `distribution plan` 结果列。 + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 为例,以下两条语句等价: + +```SQL +EXPLAIN SELECT * FROM table1; +EXPLAIN (FORMAT GRAPHVIZ) SELECT * FROM table1; +``` + +执行如上语句后,输出如下:IoTDB 通过 `DeviceTableScanNode` 节点从不同数据分区读取数据,并通过 `Collect` 算子汇总后返回。 + +```SQL ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| distribution plan| ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | +| │OutputNode-4 │ | +| │OutputColumns-[time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time] │ | +| │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ | +| └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | +| │ | +| │ | +| ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | +| │Collect-33 │ | +| │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ | +| └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | +| ┌───────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────┐ | +| │ │ | +| ┌───────────┐ ┌───────────┐ | +| │Exchange-40│ │Exchange-41│ | +| └───────────┘ └───────────┘ | +| │ │ | +| │ │ | +|┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐| +|│DeviceTableScanNode-32 │ │DeviceTableScanNode-31 │| +|│QualifiedTableName: database1.table1 │ │QualifiedTableName: database1.table1 │| +|│OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│| +|│DeviceNumber: 4 │ │DeviceNumber: 2 │| +|│ScanOrder: ASC │ │ScanOrder: ASC │| +|│PushDownOffset: 0 │ │PushDownOffset: 0 │| +|│PushDownLimit: 0 │ │PushDownLimit: 0 │| +|│PushDownLimitToEachDevice: false │ │PushDownLimitToEachDevice: false │| +|│RegionId: 1 │ │RegionId: 2 │| +|└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘| ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +``` + +### 2.3 JSON 格式 + +使用 `EXPLAIN (FORMAT JSON)` 可以返回分布式计划树的 JSON 表示。结果集为单列输出,JSON 格式下返回单行、单个 JSON 对象,便于 JDBC 客户端或自动化工具直接读取。 + +`EXPLAIN (FORMAT JSON)` 的顶层结构随查询类型变化: + +|场景|顶层结构|说明| +|---|---|---| +|普通查询|plan node JSON object|顶层直接是 `OutputNode` 等计划节点对象| +|包含 materialized CTE 的查询|wrapper JSON object|顶层包含 `cteQueries` 和 `mainQuery`,用于同时保留 CTE 子查询计划与主查询计划| + +每个 plan node 输出为一个 JSON object,基础字段如下: + +|字段|类型/出现位置|说明| +|---|---|---| +|`name`|string|节点类型与 plan node id 拼接后的展示名,例如 `OutputNode-4`| +|`id`|string|plan node id| +|`properties`|object|节点属性。仅当节点存在可展示属性时输出| +|`children`|array|子节点数组。仅当节点存在子节点时输出| + +常见 plan node 属性包括: + +|节点类型|主要字段/属性| +|---|---| +|`OutputNode`|`OutputColumns`、`OutputSymbols`| +|`TableScanNode` / `DeviceTableScanNode`|`QualifiedTableName`、`OutputSymbols`、`DeviceNumber`、`ScanOrder`、`TimePredicate`、`PushDownPredicate`、`PushDownOffset`、`PushDownLimit`、`PushDownLimitToEachDevice`、`RegionId`| +|`TreeDeviceViewScanNode`|除表扫描属性外,补充 `TreeDB`、`MeasurementToColumnName`| +|`AggregationNode`|`OutputSymbols`、`Aggregators`、`GroupingKeys`、`Streamable`、`PreGroupedSymbols`、`Step`| +|`FilterNode`|`Predicate`| +|`ProjectNode`|`OutputSymbols`、`Expressions`| +|`LimitNode` / `OffsetNode`|`Count`| +|`SortNode` / `MergeSortNode`|`OrderBy`| +|`JoinNode`|`JoinType`、`Criteria`、`OutputSymbols`| +|`UnionNode`|`OutputSymbols`| +|`ExplainAnalyzeNode`|`ChildPermittedOutputs`| + +当查询包含 materialized CTE 时,JSON 顶层不再直接是单个 plan node,而是包装为: + +```JSON +{ + "cteQueries": [ + { + "name": "cte1", + "plan": { + "name": "OutputNode-", + "id": "" + } + } + ], + "mainQuery": { + "name": "OutputNode-", + "id": "" + } +} +``` + +其中 `cteQueries[].plan` 与 `mainQuery` 均为 plan node JSON object。该结构用于同时保留 CTE 子查询计划与主查询计划。 + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 为例: + +```SQL +EXPLAIN (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "CollectNode-33", + "id": "33", + "children": [ + { + "name": "ExchangeNode-40", + "id": "40", + "children": [ + { + "name": "DeviceTableScanNode-32", + "id": "32", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-41", + "id": "41", + "children": [ + { + "name": "DeviceTableScanNode-31", + "id": "31", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] +} ++-----------------+ +``` + +## 3. EXPLAIN ANALYZE + +### 3.1 语法 + +`EXPLAIN ANALYZE` 是 IoTDB 查询引擎自带的性能分析 SQL。与 `EXPLAIN` 不同,它会真实执行对应查询计划并统计执行信息,可用于追踪一条查询的具体性能分布,辅助资源观察、性能调优和异常分析。 + +```SQL +EXPLAIN ANALYZE [VERBOSE] [(FORMAT { TEXT | JSON })] +``` + +其中: + +|参数|说明| +|---|---| +|`SELECT_STATEMENT`|需要分析的查询语句| +|`VERBOSE`|打印更详细的分析结果。不填写时会省略部分细粒度统计| +|`FORMAT TEXT`|以默认文本形式输出分析结果,适合人工阅读| +|`FORMAT JSON`|以 JSON 对象输出分析结果,适合程序解析(自 V2.0.11 起支持)| + +### 3.2 默认 TEXT 格式 + +`EXPLAIN ANALYZE` 默认格式为 `TEXT`,执行后将得到 `Explain Analyze` 结果列。结果由查询规划统计、fragment instance 统计、数据读取统计和算子树统计组成。 + +- **查询规划统计** + +`QueryStatistics` 包含查询层面的统计信息: + +|字段|类型/出现位置|说明| +|---|---|---| +|`Analyze Cost`|查询规划统计|SQL 分析阶段的耗时| +|`Fetch Partition Cost`|查询规划统计|拉取分区表的耗时| +|`Fetch Schema Cost`|查询规划统计|拉取元数据以及权限校验的耗时| +|`Logical Plan Cost`|查询规划统计|构建逻辑计划的耗时| +|`Logical Optimization Cost`|查询规划统计|逻辑计划优化的耗时| +|`Distribution Plan Cost`|查询规划统计|构建分布式计划的耗时| +|`Dispatch Cost`|查询规划统计|分发 fragment instance 的耗时| +|`Fragment Instances Count`|查询规划统计|总查询分片数量,每个查询分片的信息会依次输出| + +- **Fragment instance 统计** + +`FragmentInstance` 是 IoTDB 一个查询分片的封装。每个查询分片都会输出一份执行信息,主要包含 fragment 统计和算子信息。 + +fragment 统计包括: + +|字段|类型/出现位置|说明| +|---|---|---| +|`Total Wall Time`|fragment 统计|该分片从开始执行到执行结束的物理时间| +|`Cost of initDataQuerySource`|fragment 统计|构建查询文件列表的耗时| +|`Seq File(unclosed)`|fragment 统计|未封口的顺序文件数量| +|`Seq File(closed)`|fragment 统计|已封口的顺序文件数量| +|`UnSeq File(unclosed)`|fragment 统计|未封口的乱序文件数量| +|`UnSeq File(closed)`|fragment 统计|已封口的乱序文件数量| +|`ready queued time`|fragment 统计|查询分片所有 task 在 ready queue 中的总时长| +|`blocked queued time`|fragment 统计|查询分片所有 task 在 blocked queue 中的总时长| + +- **数据读取统计** + +`Query Statistics` 明细字段包括: + +|字段|类型/出现位置|说明| +|---|---|---| +|`loadBloomFilterFromCacheCount`|BloomFilter|命中 BloomFilterCache 的次数| +|`loadBloomFilterFromDiskCount`|BloomFilter|从磁盘读取 BloomFilter 的次数| +|`loadBloomFilterActualIOSize`|BloomFilter|从磁盘读取 BloomFilter 时产生的磁盘 IO,单位为 bytes| +|`loadBloomFilterTime`|BloomFilter|读取 BloomFilter 并计算序列是否存在的总耗时,单位为 ms| +|`loadTimeSeriesMetadataDiskSeqCount`|TimeSeriesMetadata|从已封口顺序文件中加载的 TimeSeriesMetadata 数量| +|`loadTimeSeriesMetadataDiskUnSeqCount`|TimeSeriesMetadata|从已封口乱序文件中加载的 TimeSeriesMetadata 数量| +|`loadTimeSeriesMetadataDiskSeqTime`|TimeSeriesMetadata|从已封口顺序文件中加载 TimeSeriesMetadata 的耗时| +|`loadTimeSeriesMetadataDiskUnSeqTime`|TimeSeriesMetadata|从已封口乱序文件中加载 TimeSeriesMetadata 的耗时| +|`loadTimeSeriesMetadataFromCacheCount`|TimeSeriesMetadata|命中 TimeSeriesMetadataCache 的次数| +|`loadTimeSeriesMetadataFromDiskCount`|TimeSeriesMetadata|从磁盘读取 TimeSeriesMetadata 的次数| +|`loadTimeSeriesMetadataActualIOSize`|TimeSeriesMetadata|从磁盘读取 TimeSeriesMetadata 时产生的磁盘 IO,单位为 bytes| +|`TimeSeriesMetadataModificationTime`|Mods 文件|读取 mods 文件的耗时| +|`constructAlignedChunkReadersDiskCount`|Chunk|构造 ChunkReader 的次数| +|`constructAlignedChunkReadersDiskTime`|Chunk|构造 ChunkReader 的总耗时,包含磁盘 IO 和解压缩| +|`pageReadersDecodeAlignedDiskCount`|Chunk|解码 page 的数量| +|`pageReadersDecodeAlignedDiskTime`|Chunk|解码 page 的总耗时| +|`loadChunkFromCacheCount`|Chunk|命中 ChunkCache 的次数| +|`loadChunkFromDiskCount`|Chunk|从磁盘读取 Chunk 的次数| +|`loadChunkActualIOSize`|Chunk|从磁盘读取 Chunk 时产生的磁盘 IO,单位为 bytes| + +- **算子树统计** + +算子树统计包括: + +|字段|类型/出现位置|说明| +|---|---|---| +|`CPU Time`|算子树统计|当前算子的 CPU 执行耗时| +|`output`|算子树统计|当前算子的输出行数| +|`HasNext() Called Count`|算子树统计|当前算子 `HasNext()` 接口被调用的次数| +|`Next() Called Count`|算子树统计|当前算子 `Next()` 接口被调用的次数| +|`Estimated Memory Size`|算子树统计|当前算子的估算内存占用| +|节点专属统计信息|算子树统计|不同算子输出的定制统计字段,例如 `DeviceNumber`、`CurrentDeviceIndex`、`OutputPlanNodeId`、`size_in_bytes` 等| + +常见节点专属统计字段包括: + +|字段|类型/出现位置|说明| +|---|---|---| +|`DeviceNumber`|`TableScan` 相关节点|当前 table scan 涉及的设备数量| +|`CurrentDeviceIndex`|`TableScan` 相关节点|当前正在扫描的设备索引| +|`OutputPlanNodeId`|sink 节点|sink 节点对应的下游接收数据节点| +|`size_in_bytes`|exchange 节点|exchange 节点接收到的 TsBlock 字节数,仅计算数据占用大小| +|`TimeSeriesIndexFilteredRows`|filter 下推到 table scan 时的 table scan 节点|通过序列元数据过滤掉的数据行数| +|`ChunkIndexFilteredRows`|filter 下推到 table scan 时的 table scan 节点|通过列块元数据过滤掉的数据行数| +|`PageIndexFilteredRows`|filter 下推到 table scan 时的 table scan 节点|通过页头内部统计信息过滤掉的数据行数| +|`RowScanFilteredRows`|filter 下推到 table scan 且使用 `VERBOSE` 时的 table scan 节点|逐行检查数据时被过滤掉的行数| + +实际输出字段会随查询类型、涉及的算子、是否使用 `VERBOSE` 以及是否触发过滤下推而变化。未触发对应逻辑时,相关字段可能不会出现在结果中。 + +- **文本输出示例** + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 为例,以下两条语句等价: + +```SQL +EXPLAIN ANALYZE SELECT * FROM table1; +EXPLAIN ANALYZE (FORMAT TEXT) SELECT * FROM table1; +``` + +```SQL ++-------------------------------------------------------------------------------------------------------------------------+ +| Explain Analyze| ++-------------------------------------------------------------------------------------------------------------------------+ +|Analyze Cost: 3.862 ms | +|Fetch Partition Cost: 0.785 ms | +|Fetch Schema Cost: 5.473 ms | +|Logical Plan Cost: 37.350 ms | +|Logical Optimization Cost: 4.340 ms | +|Distribution Plan Cost: 1.287 ms | +|Dispatch Cost: 8.711 ms | +|Fragment Instances Count: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.2.0][IP: 127.0.0.1:10730][DataRegion: virtual_data_region][State: FINISHED]| +| Total Wall Time: 64 ms | +| Cost of initDataQuerySource: 0.000 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.428 ms, blocked queued time: 11.410 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 69]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 28.691 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| DownStreamPlanNodeId: 66 | +| [PlanNodeId 55]: CollectNode(CollectOperator) | +| CPU Time: 28.656 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| [PlanNodeId 64]: ExchangeNode(ExchangeOperator) | +| CPU Time: 13.310 ms | +| output: 12 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 3264 | +| [PlanNodeId 65]: ExchangeNode(ExchangeOperator) | +| CPU Time: 15.286 ms | +| output: 6 rows | +| HasNext() Called Count: 3 | +| Next() Called Count: 2 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 1632 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.3.0][IP: 127.0.0.1:10730][DataRegion: 1][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 7.652 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 2, UnSeq File(closed): 0 | +| ready queued time: 0.089 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 67]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 2.007 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 64 | +| [PlanNodeId 54]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 1.981 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 4 | +| CurrentDeviceIndex: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.4.0][IP: 127.0.0.1:10730][DataRegion: 2][State: FINISHED] | +| Total Wall Time: 48 ms | +| Cost of initDataQuerySource: 0.933 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 1, UnSeq File(closed): 0 | +| ready queued time: 0.141 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 68]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 1.846 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 65 | +| [PlanNodeId 53]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 1.755 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 2 | +| CurrentDeviceIndex: 1 | ++-------------------------------------------------------------------------------------------------------------------------+ +``` + +- **VERBOSE 输出示例** + +使用 `VERBOSE` 可以查看更细粒度的数据读取和过滤统计: + +```SQL +EXPLAIN ANALYZE VERBOSE SELECT * FROM table1; +EXPLAIN ANALYZE VERBOSE (FORMAT TEXT) SELECT * FROM table1; +``` + +```YAML ++-------------------------------------------------------------------------------------------------------------------------+ +| Explain Analyze| ++-------------------------------------------------------------------------------------------------------------------------+ +|Analyze Cost: 1.016 ms | +|Fetch Partition Cost: 0.555 ms | +|Fetch Schema Cost: 3.789 ms | +|Logical Plan Cost: 0.491 ms | +|Logical Optimization Cost: 2.625 ms | +|Distribution Plan Cost: 0.393 ms | +|Dispatch Cost: 37.041 ms | +|Fragment Instances Count: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.2.0][IP: 127.0.0.1:10730][DataRegion: virtual_data_region][State: FINISHED]| +| Total Wall Time: 43 ms | +| Cost of initDataQuerySource: 0.000 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.248 ms, blocked queued time: 23.757 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 0 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.000 | +| loadTimeSeriesMetadataFromCacheCount: 0 | +| loadTimeSeriesMetadataFromDiskCount: 0 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| loadChunkFromCacheCount: 0 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 69]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 0.988 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| DownStreamPlanNodeId: 66 | +| [PlanNodeId 55]: CollectNode(CollectOperator) | +| CPU Time: 0.954 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| [PlanNodeId 64]: ExchangeNode(ExchangeOperator) | +| CPU Time: 0.569 ms | +| output: 12 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 3264 | +| [PlanNodeId 65]: ExchangeNode(ExchangeOperator) | +| CPU Time: 0.354 ms | +| output: 6 rows | +| HasNext() Called Count: 3 | +| Next() Called Count: 2 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 1632 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.3.0][IP: 127.0.0.1:10730][DataRegion: 1][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 0.224 ms | +| Seq File(unclosed): 0, Seq File(closed): 2 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.025 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 2 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.018 | +| loadTimeSeriesMetadataAlignedDiskSeqCount: 4 | +| loadTimeSeriesMetadataAlignedDiskSeqTime: 0.189 | +| loadTimeSeriesMetadataFromCacheCount: 18 | +| loadTimeSeriesMetadataFromDiskCount: 2 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| alignedTimeSeriesMetadataModificationTime: 0.026 | +| constructAlignedChunkReadersDiskCount: 4 | +| constructAlignedChunkReadersDiskTime: 3.251 | +| loadChunkFromCacheCount: 18 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| pageReadersDecodeAlignedDiskCount: 4 | +| pageReadersDecodeAlignedDiskTime: 0.210 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 67]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 5.150 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 64 | +| [PlanNodeId 54]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 5.136 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 4 | +| CurrentDeviceIndex: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.4.0][IP: 127.0.0.1:10730][DataRegion: 2][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 0.397 ms | +| Seq File(unclosed): 0, Seq File(closed): 1 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.075 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 0 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.000 | +| loadTimeSeriesMetadataAlignedDiskSeqCount: 2 | +| loadTimeSeriesMetadataAlignedDiskSeqTime: 0.313 | +| loadTimeSeriesMetadataFromCacheCount: 10 | +| loadTimeSeriesMetadataFromDiskCount: 0 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| alignedTimeSeriesMetadataModificationTime: 0.046 | +| constructAlignedChunkReadersDiskCount: 2 | +| constructAlignedChunkReadersDiskTime: 2.307 | +| loadChunkFromCacheCount: 10 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| pageReadersDecodeAlignedDiskCount: 2 | +| pageReadersDecodeAlignedDiskTime: 0.129 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 68]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 4.187 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 65 | +| [PlanNodeId 53]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 4.172 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 2 | +| CurrentDeviceIndex: 1 | ++-------------------------------------------------------------------------------------------------------------------------+ +``` + +### 3.3 JSON 格式 + +使用 `EXPLAIN ANALYZE (FORMAT JSON)` 可以返回计划阶段耗时、fragment instance 统计、operator 统计等结构化信息。JSON 格式自 V2.0.11 起支持。 + +#### 3.3.1 默认 JSON 输出 + +```SQL +EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM t1; +``` + +顶层 JSON 结构如下: + +|字段|类型/出现位置|说明| +|---|---|---| +|`planStatistics`|object|查询分析、分区获取、schema 获取、逻辑规划、逻辑优化、分布式规划、dispatch 等阶段耗时| +|`fragmentInstancesCount`|number|返回统计信息的 fragment instance 数量| +|`fragmentInstances`|array|各 fragment instance 的执行统计| + +`planStatistics` 主要字段如下: + +|字段|类型/出现位置|说明| +|---|---|---| +|`analyzeCostMs`|planStatistics|SQL 分析耗时,单位 ms| +|`fetchPartitionCostMs`|planStatistics|获取分区信息耗时,单位 ms| +|`fetchSchemaCostMs`|planStatistics|获取 schema 信息耗时,单位 ms| +|`logicalPlanCostMs`|planStatistics|逻辑计划生成耗时,单位 ms| +|`logicalOptimizationCostMs`|planStatistics|逻辑优化耗时,单位 ms| +|`distributionPlanCostMs`|planStatistics|分布式计划生成耗时,单位 ms| +|`dispatchCostMs`|planStatistics|fragment dispatch 耗时,单位 ms| + +`fragmentInstances[]` 主要字段如下: + +|字段|类型/出现位置|说明| +|---|---|---| +|`id`|fragmentInstances[]|fragment instance id| +|`ip`|fragmentInstances[]|执行该 fragment instance 的节点 IP| +|`dataRegion`|fragmentInstances[]|对应 DataRegion| +|`state`|fragmentInstances[]|fragment instance 最终状态| +|`totalWallTimeMs`|fragmentInstances[]|总 wall time,单位 ms| +|`initDataQuerySourceCostMs`|fragmentInstances[]|初始化数据源耗时,单位 ms| +|`initDataQuerySourceRetryCount`|fragmentInstances[]|初始化数据源重试次数。仅当大于 0 时输出| +|`seqFileUnclosed`、`seqFileClosed`、`unseqFileUnclosed`、`unseqFileClosed`|fragmentInstances[]|涉及的顺序和乱序文件数量| +|`readyQueuedTimeMs`、`blockQueuedTimeMs`|fragmentInstances[]|ready / block 队列等待时间,单位 ms| +|`queryStatistics`|fragmentInstances[]|扫描、过滤、chunk/page reader 等查询统计| +|`operators`|fragmentInstances[]|operator tree 及每个 operator 的执行统计| + +`operators` 为与 plan tree 对应的树状结构,主要字段如下: + +|字段|类型/出现位置|说明| +|---|---|---| +|`planNodeId`|operators|对应 plan node id| +|`nodeType`|operators|plan node 类型| +|`operatorType`|operators|实际执行 operator 类型| +|`count`|operators|operator 统计聚合次数。仅当存在时输出| +|`cpuTimeMs`|operators|operator CPU 执行耗时,单位 ms| +|`outputRows`|operators|输出行数| +|`hasNextCalledCount`、`nextCalledCount`|operators|operator 迭代调用次数| +|`estimatedMemorySize`|operators|估算内存占用。仅非 0 时输出| +|`specifiedInfo`|operators|operator 特有统计信息。仅非空时输出| +|`children`|operators|子 operator 数组| + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 为例: + +```SQL +EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 4.948, + "fetchPartitionCostMs": 2.104, + "fetchSchemaCostMs": 13.321, + "logicalPlanCostMs": 2.264, + "logicalOptimizationCostMs": 7.941, + "distributionPlanCostMs": 1.434, + "dispatchCostMs": 9.023 + }, + "fragmentInstancesCount": 3, + "fragmentInstances": [ + { + "id": "20260722_064302_00101_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "virtual_data_region", + "state": "FINISHED", + "totalWallTimeMs": 97, + "initDataQuerySourceCostMs": 0.0, + "seqFileUnclosed": 0, + "seqFileClosed": 0, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.858, + "blockQueuedTimeMs": 71.752, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "69", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 6.082, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "DownStreamPlanNodeId": "66" + }, + "children": [ + { + "planNodeId": "55", + "nodeType": "CollectNode", + "operatorType": "CollectOperator", + "cpuTimeMs": 6.065, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "children": [ + { + "planNodeId": "64", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 1.119, + "outputRows": 12, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "3264" + } + }, + { + "planNodeId": "65", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 4.909, + "outputRows": 6, + "hasNextCalledCount": 3, + "nextCalledCount": 2, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "1632" + } + } + ] + } + ] + } + }, + { + "id": "20260722_064302_00101_1.3.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 63, + "initDataQuerySourceCostMs": 0.338, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.273, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "67", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 57.338, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "64" + }, + "children": [ + { + "planNodeId": "54", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 57.248, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "4", + "CurrentDeviceIndex": "3" + } + } + ] + } + }, + { + "id": "20260722_064302_00101_1.4.0", + "ip": "127.0.0.1:10730", + "dataRegion": "2", + "state": "FINISHED", + "totalWallTimeMs": 79, + "initDataQuerySourceCostMs": 0.231, + "seqFileUnclosed": 0, + "seqFileClosed": 1, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.038, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "68", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 66.387, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "65" + }, + "children": [ + { + "planNodeId": "53", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 66.362, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "2", + "CurrentDeviceIndex": "1" + } + } + ] + } + } + ] +} ++---------------+ +``` + +#### 3.3.2 VERBOSE JSON 输出 + +当使用 `EXPLAIN ANALYZE VERBOSE (FORMAT JSON)` 时,顶层结构与非 verbose JSON 一致,但 `queryStatistics` 会在基础过滤行数外补充更细粒度统计,包括 bloom filter、time series metadata、chunk、page reader、modification 和实际 IO size 等信息。 + +`VERBOSE` 与非 `VERBOSE` JSON 输出的主要差异如下: + +|输出模式|顶层结构|`queryStatistics` 字段粒度|典型字段| +|---|---|---|---| +|`EXPLAIN ANALYZE (FORMAT JSON)`|`planStatistics`、`fragmentInstancesCount`、`fragmentInstances`|默认统计字段|`timeSeriesIndexFilteredRows`、`chunkIndexFilteredRows`、`pageIndexFilteredRows`| +|`EXPLAIN ANALYZE VERBOSE (FORMAT JSON)`|与非 `VERBOSE` 一致|更详细的扫描、读取和过滤统计|`loadBloomFilterFromCacheCount`、`loadTimeSeriesMetadataFromCacheCount`、`loadChunkFromDiskCount`、`pageReadersDecodeAlignedMemCount`、`rowScanFilteredRows`| + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 为例: + +```SQL +EXPLAIN ANALYZE VERBOSE (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 3.505, + "fetchPartitionCostMs": 2.003, + "fetchSchemaCostMs": 9.198, + "logicalPlanCostMs": 1.481, + "logicalOptimizationCostMs": 6.684, + "distributionPlanCostMs": 0.711, + "dispatchCostMs": 8.32 + }, + "fragmentInstancesCount": 3, + "fragmentInstances": [ + { + "id": "20260722_064502_00103_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "virtual_data_region", + "state": "FINISHED", + "totalWallTimeMs": 48, + "initDataQuerySourceCostMs": 0.0, + "seqFileUnclosed": 0, + "seqFileClosed": 0, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.209, + "blockQueuedTimeMs": 17.997, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 0, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.0, + "loadTimeSeriesMetadataFromCacheCount": 0, + "loadTimeSeriesMetadataFromDiskCount": 0, + "loadTimeSeriesMetadataActualIOSize": 0, + "loadChunkFromCacheCount": 0, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "69", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 26.368, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "DownStreamPlanNodeId": "66" + }, + "children": [ + { + "planNodeId": "55", + "nodeType": "CollectNode", + "operatorType": "CollectOperator", + "cpuTimeMs": 26.32, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "children": [ + { + "planNodeId": "64", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 0.47, + "outputRows": 12, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "3264" + } + }, + { + "planNodeId": "65", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 25.802, + "outputRows": 6, + "hasNextCalledCount": 3, + "nextCalledCount": 2, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "1632" + } + } + ] + } + ] + } + }, + { + "id": "20260722_064502_00103_1.3.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 21, + "initDataQuerySourceCostMs": 0.232, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.007, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 2, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.02, + "loadTimeSeriesMetadataAlignedDiskSeqCount": 4, + "loadTimeSeriesMetadataAlignedDiskSeqTimeMs": 0.483, + "loadTimeSeriesMetadataFromCacheCount": 18, + "loadTimeSeriesMetadataFromDiskCount": 2, + "loadTimeSeriesMetadataActualIOSize": 0, + "alignedTimeSeriesMetadataModificationTimeMs": 0.061, + "constructAlignedChunkReadersDiskCount": 4, + "constructAlignedChunkReadersDiskTimeMs": 2.693, + "loadChunkFromCacheCount": 18, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "pageReadersDecodeAlignedDiskCount": 4, + "pageReadersDecodeAlignedDiskTimeMs": 0.22, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "67", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 5.002, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "64" + }, + "children": [ + { + "planNodeId": "54", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 4.94, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "4", + "CurrentDeviceIndex": "3" + } + } + ] + } + }, + { + "id": "20260722_064502_00103_1.4.0", + "ip": "127.0.0.1:10730", + "dataRegion": "2", + "state": "FINISHED", + "totalWallTimeMs": 32, + "initDataQuerySourceCostMs": 0.253, + "seqFileUnclosed": 0, + "seqFileClosed": 1, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.027, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 0, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.0, + "loadTimeSeriesMetadataAlignedDiskSeqCount": 2, + "loadTimeSeriesMetadataAlignedDiskSeqTimeMs": 0.188, + "loadTimeSeriesMetadataFromCacheCount": 10, + "loadTimeSeriesMetadataFromDiskCount": 0, + "loadTimeSeriesMetadataActualIOSize": 0, + "alignedTimeSeriesMetadataModificationTimeMs": 0.037, + "constructAlignedChunkReadersDiskCount": 2, + "constructAlignedChunkReadersDiskTimeMs": 1.807, + "loadChunkFromCacheCount": 10, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "pageReadersDecodeAlignedDiskCount": 2, + "pageReadersDecodeAlignedDiskTimeMs": 0.62, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "68", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 3.652, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "65" + }, + "children": [ + { + "planNodeId": "53", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 3.633, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "2", + "CurrentDeviceIndex": "1" + } + } + ] + } + } + ] +} +``` + +## 4. 场景示例 + +### 4.1 CTE 查询计划 + +当 `EXPLAIN (FORMAT JSON)` 分析包含 materialized CTE 的查询时,输出会使用 `cteQueries` \+ `mainQuery` 的包装结构。 + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 和 `table2` 为例: + +```SQL +EXPLAIN (FORMAT JSON) +WITH cte1 AS MATERIALIZED (SELECT * FROM table2) +SELECT * FROM table1 +WHERE table1.device_id IN (SELECT device_id FROM cte1); +``` + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "cteQueries": [ + { + "name": "cte1", + "plan": { + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "CollectNode-33", + "id": "33", + "children": [ + { + "name": "ExchangeNode-40", + "id": "40", + "children": [ + { + "name": "DeviceTableScanNode-32", + "id": "32", + "properties": { + "QualifiedTableName": "database1.table2", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-41", + "id": "41", + "children": [ + { + "name": "DeviceTableScanNode-31", + "id": "31", + "properties": { + "QualifiedTableName": "database1.table2", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] + } + } + ], + "mainQuery": { + "name": "OutputNode-12", + "id": "12", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "ProjectNode-35", + "id": "35", + "properties": { + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "Expressions": [ + "\"time\"", + "\"region\"", + "\"plant_id\"", + "\"device_id\"", + "\"model_id\"", + "\"maintenance\"", + "\"temperature\"", + "\"humidity\"", + "\"status\"", + "\"arrival_time\"" + ] + }, + "children": [ + { + "name": "FilterNode-138", + "id": "138", + "properties": { + "Predicate": "\"expr\"" + }, + "children": [ + { + "name": "SemiJoinNode-109", + "id": "109", + "children": [ + { + "name": "ExchangeNode-218", + "id": "218", + "children": [ + { + "name": "MergeSortNode-197", + "id": "197", + "properties": { + "OrderBy": "{orderBy\u003d[device_id], orderings\u003d{device_id\u003dASC NULLS LAST}}" + }, + "children": [ + { + "name": "ExchangeNode-216", + "id": "216", + "children": [ + { + "name": "DeviceTableScanNode-196", + "id": "196", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "true", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-217", + "id": "217", + "children": [ + { + "name": "DeviceTableScanNode-195", + "id": "195", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "true", + "RegionId": "2" + } + } + ] + } + ] + } + ] + }, + { + "name": "ExchangeNode-219", + "id": "219", + "children": [ + { + "name": "SortNode-137", + "id": "137", + "properties": { + "OrderBy": "{orderBy\u003d[device_id_3], orderings\u003d{device_id_3\u003dASC NULLS FIRST}}" + }, + "children": [ + { + "name": "ProjectNode-6", + "id": "6", + "properties": { + "OutputSymbols": [ + "device_id_3" + ], + "Expressions": [ + "\"device_id_3\"" + ] + }, + "children": [ + { + "name": "CteScanNode-2", + "id": "2" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } +} ++-----------------+ +``` + +### 4.2 Prepared Statement 与动态 SQL + +通过 `EXECUTE` 或 `EXECUTE IMMEDIATE` 分析预处理语句和动态 SQL 时,指定的输出格式会在语句展开后继续生效,不会退回默认的 `GRAPHVIZ` 或 `TEXT` 格式。 + +```SQL +PREPARE explain_json_stmt FROM SELECT * FROM table1 WHERE device_id = ?; +EXPLAIN (FORMAT JSON) EXECUTE explain_json_stmt USING '101'; + +EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE 'SELECT * FROM table1 WHERE device_id = ?' USING '101'; +``` + +其中: + +- `EXPLAIN (FORMAT JSON) EXECUTE ...` 返回 plan node JSON object。 + +- `EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE ...` 返回包含 `planStatistics`、`fragmentInstancesCount`、`fragmentInstances` 的 JSON object。 + +1. `EXPLAIN (FORMAT JSON) EXECUTE ...` 输出结构示例: + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-5", + "id": "5", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "DeviceTableScanNode-46", + "id": "46", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] +} ++-----------------+ +``` + +2. `EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE ...` 输出结构示例: + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 2.904, + "fetchPartitionCostMs": 1.35, + "fetchSchemaCostMs": 5.259, + "logicalPlanCostMs": 1.123, + "logicalOptimizationCostMs": 6.157, + "distributionPlanCostMs": 0.571, + "dispatchCostMs": 5.909 + }, + "fragmentInstancesCount": 1, + "fragmentInstances": [ + { + "id": "20260722_065558_00118_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 10, + "initDataQuerySourceCostMs": 1.768, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.059, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "74", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 9.193, + "outputRows": 10, + "hasNextCalledCount": 7, + "nextCalledCount": 6, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "73" + }, + "children": [ + { + "planNodeId": "68", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 9.055, + "outputRows": 10, + "hasNextCalledCount": 7, + "nextCalledCount": 6, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "3", + "CurrentDeviceIndex": "2" + } + } + ] + } + } + ] +} ++---------------+ +``` + +### 4.3 CLI 中的 JSON 展示 + +CLI 对普通查询结果默认按表格输出。对于 JSON 格式的查询分析结果,如果继续把 JSON 正文放入表格单元格,每一行都会带上 `|` 边框,不利于复制、保存和 JSON 解析。 + +当满足以下条件时,CLI 会切换为 raw JSON 展示: + +|条件|说明| +|---|---| +|结果集只有一列|避免影响普通多列查询展示| +|列名为 `distribution plan` 或 `Explain Analyze`|仅匹配 `EXPLAIN` / `EXPLAIN ANALYZE` 的结果列| +|首个结果值 trim 后以 `{` 或 `[` 开始|仅对 JSON object / array 内容启用 raw 输出| + +raw JSON 展示规则: + +- CLI 仍输出列名表头和外层分隔线,用户可以识别当前列为 `distribution plan` 或 `Explain Analyze`。 + +- JSON 正文逐行原样输出,不添加 `|` 表格边框。 + +- JSON 结束后输出分隔线和行数统计。 + +- 非 JSON 的 `EXPLAIN`、默认文本 `EXPLAIN ANALYZE`、普通查询结果仍走原有表格输出。 + +raw JSON 展示与普通表格输出的区别如下: + +|输出类型|适用结果|展示方式|复制与解析| +|---|---|---|---| +|普通表格输出|普通查询结果、默认 `EXPLAIN`、默认 `EXPLAIN ANALYZE`|每一行内容都放在表格边框内|适合人工查看,不适合直接作为 JSON 复制解析| +|raw JSON 输出|`EXPLAIN (FORMAT JSON)`、`EXPLAIN ANALYZE (FORMAT JSON)`|保留列名表头,JSON 正文不加表格边框|适合直接复制、保存或交给 JSON parser 解析| + +示例: + +```SQL +start-cli.sh -sql_dialect table -e "EXPLAIN (FORMAT JSON) SELECT time, device_id, temperature FROM database1.table1" +``` + +```SQL ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "device_id", + "temperature" + ], + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ] + }, + "children": [ + { + "name": "CollectNode-42", + "id": "42", + "children": [ + { + "name": "ExchangeNode-49", + "id": "49", + "children": [ + { + "name": "DeviceTableScanNode-41", + "id": "41", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-50", + "id": "50", + "children": [ + { + "name": "DeviceTableScanNode-40", + "id": "40", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] +} ++-----------------+ +``` + +### 4.4 非法格式组合 + +`EXPLAIN` 和 `EXPLAIN ANALYZE` 支持的格式不同。非法格式组合会返回明确错误。 + +|语句|默认格式|支持格式|不支持格式| +|---|---|---|---| +|`EXPLAIN`|`GRAPHVIZ`|`GRAPHVIZ`、`JSON`|`TEXT`、`XML`、其他未知格式| +|`EXPLAIN ANALYZE`|`TEXT`|`TEXT`、`JSON`|`GRAPHVIZ`、`XML`、其他未知格式| + +示例: + +```SQL +EXPLAIN (FORMAT TEXT) SELECT * FROM table1; +EXPLAIN ANALYZE (FORMAT GRAPHVIZ) SELECT * FROM table1; +EXPLAIN (FORMAT XML) SELECT * FROM table1; +``` + +对应报错: + +```SQL +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN format: TEXT. Supported formats: GRAPHVIZ, JSON +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN ANALYZE format: GRAPHVIZ. Supported formats: TEXT, JSON +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN format: XML. Supported formats: GRAPHVIZ, JSON +``` + +## 5. 常见问题 + +### 5.1 查询超时时怎么办? + +`EXPLAIN ANALYZE` 会真实执行查询,因此也可能在查询超时时无法返回完整结果。为辅助排查,IoTDB 会按一定时间间隔将当前分析结果以文本形式写入专用日志;日志间隔根据查询超时时间计算,可保证超时前至少记录两次结果。 + +遇到超时时,按以下顺序处理: + +1. 查看 `logs/log_explain_analyze.log` 中对应查询的阶段性分析结果。 + +2. 如果日志中没有结果,检查升级时是否只替换了 lib 包,而未同步替换 `conf/logback-datanode.xml`。 + +3. 替换配置后无需重启。等待配置热加载完成,再执行 `EXPLAIN ANALYZE VERBOSE`。 + +### 5.2 WALL TIME 和 CPU TIME 有什么区别? + +CPU 时间指程序在执行过程中实际占用 CPU 进行计算的时间,表示程序实际消耗的处理器资源。 + +墙上时间指从程序开始执行到结束的真实物理时间,包括资源等待时间。 + +`WALL TIME < CPU TIME` 常见于并行执行场景。例如一个查询分片被两个线程并行执行,物理时间过去 10 秒,但两个线程各占用一个 CPU 核运行 10 秒,则 CPU TIME 约为 20 秒,WALL TIME 约为 10 秒。 + +`WALL TIME > CPU TIME` 常见于等待资源场景。例如查询分片因内存不足或等待上游数据进入 blocked queue,或者因查询线程资源不足进入 ready queue。等待期间不占用 CPU,但物理时间仍在流逝。 + +### 5.3 EXPLAIN ANALYZE 是否有额外开销? + +统计信息采集本身几乎没有显著的额外开销。`EXPLAIN ANALYZE` 算子会收集原查询已有的统计信息,并通过 `next` 遍历但不打印查询结果,因此与直接执行同一查询的耗时通常没有显著差别。 + +需要注意的是,被分析的查询仍会完整执行并正常消耗 CPU、内存和 IO 资源。评估生产环境影响时,应以原查询本身的资源消耗为准。 + +### 5.4 IO 耗时主要关注哪些指标? + +涉及 IO 耗时的指标主要包括 `loadBloomFilterActualIOSize`、`loadBloomFilterTime`、`loadTimeSeriesMetadataAlignedDiskSeqTime`、`loadTimeSeriesMetadataAlignedDiskUnseqTime`、`loadTimeSeriesMetadataActualIOSize`、`alignedTimeSeriesMetadataModificationTime`、`constructAlignedChunkReadersDiskTime`、`loadChunkActualIOSize`。 + +TimeSeriesMetadata 的加载分别统计顺序文件和乱序文件,但 Chunk 的读取暂时未分开统计顺序和乱序比例,可以通过 TimeSeriesMetadata 的顺乱序比例进行估算。 + +### 5.5 乱序数据对查询性能的影响如何观测? + +乱序数据主要产生两类影响: + +1. 查询时需要在内存中多做一次归并排序。 + +2. 乱序数据会产生数据块间的时间范围重叠,导致部分统计信息无法直接用于跳过不满足条件的数据块,或无法直接计算聚合值。 + +当前没有单独针对乱序数据影响的直接观测指标。通常可以在存在乱序数据时执行一次查询,待乱序数据合并完成后再次执行查询,通过前后耗时对比进行评估。 diff --git a/src/zh/UserGuide/latest-Table/SQL-Manual/SQL-Maintenance-Statements_apache.md b/src/zh/UserGuide/latest-Table/SQL-Manual/SQL-Maintenance-Statements_apache.md index c38314a40..630493e2d 100644 --- a/src/zh/UserGuide/latest-Table/SQL-Manual/SQL-Maintenance-Statements_apache.md +++ b/src/zh/UserGuide/latest-Table/SQL-Manual/SQL-Maintenance-Statements_apache.md @@ -493,7 +493,7 @@ IoTDB> KILL ALL QUERIES; -- 终止所有query EXPLAIN ``` -更多详细语法说明请参考:[EXPLAIN 语句](../User-Manual/Query-Performance-Analysis.md#_1-explain-语句) +更多详细语法说明请参考:[EXPLAIN 语句](../User-Manual/Query-Performance-Analysis_apache.md#_2-explain) **示例:** @@ -542,7 +542,7 @@ IoTDB> explain select * from t1 EXPLAIN ANALYZE [VERBOSE] ``` -更多详细语法说明请参考:[EXPLAIN ANALYZE 语句](../User-Manual/Query-Performance-Analysis.md#_2-explain-analyze-语句) +更多详细语法说明请参考:[EXPLAIN ANALYZE 语句](../User-Manual/Query-Performance-Analysis_apache.md#_3-explain-analyze) **示例:** diff --git a/src/zh/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis.md b/src/zh/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis.md deleted file mode 100644 index aa7a4fd96..000000000 --- a/src/zh/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis.md +++ /dev/null @@ -1,342 +0,0 @@ - -# 查询性能分析 - -查询分析的意义在于帮助用户理解查询的执行机制和性能瓶颈,从而实现查询优化和性能提升。这不仅关乎到查询的执行效率,也直接影响到应用的用户体验和资源的有效利用。为了进行有效的查询分析,IoTDB 提供了查询分析语句:Explain 和 Explain Analyze。 - -- Explain 语句:允许用户预览查询 SQL 的执行计划,包括 IoTDB 如何组织数据检索和处理。 -- Explain Analyze 语句:在 Explain 语句基础上增加了性能分析,完整执行 SQL 并展示查询执行过程中的时间和资源消耗。为 IoTDB 用户深入理解查询详情以及进行查询优化提供了详细的相关信息。与其他常用的 IoTDB 排查手段相比,Explain Analyze 没有部署负担,同时能够针对单条 sql 进行分析,能够更好定位问题。各类方法对比如下:: - -| 方法 | 安装难度 | 业务影响 | 功能范围 | -| ------------------- | ------------------------------------------------------------ | ---------------------------------------------------- | ------------------------------------------------------ | -| Explain Analyze语句 | 低。无需安装额外组件,为IoTDB内置SQL语句 | 低。只会影响当前分析的单条查询,对线上其他负载无影响 | 支持分布式,可支持对单条SQL进行追踪 | -| 监控面板 | 中。需要安装IoTDB监控面板工具,并开启IoTDB监控服务 | 中。IoTDB监控服务记录指标会带来额外耗时 | 支持分布式,仅支持对数据库整体查询负载和耗时进行分析 | -| Arthas抽样 | 中。需要安装Java Arthas工具(部分内网无法直接安装Arthas,且安装后,有时需要重启应用) | 高。CPU 抽样可能会影响线上业务的响应速度 | 不支持分布式,仅支持对数据库整体查询负载和耗时进行分析 | - -## 1. Explain 语句 - -#### 语法 - -Explain命令允许用户查看SQL查询的执行计划。执行计划以算子的形式展示,描述了IoTDB会如何执行查询。其语法如下,其中SELECT_STATEMENT是查询相关的SQL语句: - -```SQL -EXPLAIN -``` -#### 含义 - -```SQL --- 创建数据库 -create database test; - --- 创建表 -use test; -create table t1 (device_id STRING ID, type STRING ATTRIBUTE, speed float); - --- 插入数据 -insert into t1(device_id, type, speed) values('car_1', 'Model Y', 120.0); -insert into t1(device_id, type, speed) values('car_2', 'Model 3', 100.0); - --- 执行 explain 语句 -explain select * from t1; -``` -执行上方SQL,会得到如下结果。不难看出,IoTDB 分别通过两个 TableScan 节点去不同的数据分区中获取表中的数据,最后通过Collect 算子汇总数据后返回。 - -```SQL -+-----------------------------------------------------------------------------------------------+ -| distribution plan| -+-----------------------------------------------------------------------------------------------+ -| ┌─────────────────────────────────────────────┐ | -| │OutputNode-4 │ | -| │OutputColumns-[time, device_id, type, speed] │ | -| │OutputSymbols: [time, device_id, type, speed]│ | -| └─────────────────────────────────────────────┘ | -| │ | -| │ | -| ┌─────────────────────────────────────────────┐ | -| │Collect-21 │ | -| │OutputSymbols: [time, device_id, type, speed]│ | -| └─────────────────────────────────────────────┘ | -| ┌───────────────────────┴───────────────────────┐ | -| │ │ | -|┌─────────────────────────────────────────────┐ ┌───────────┐ | -|│TableScan-19 │ │Exchange-28│ | -|│QualifiedTableName: test.t1 │ └───────────┘ | -|│OutputSymbols: [time, device_id, type, speed]│ │ | -|│DeviceNumber: 1 │ │ | -|│ScanOrder: ASC │ ┌─────────────────────────────────────────────┐| -|│PushDownOffset: 0 │ │TableScan-20 │| -|│PushDownLimit: 0 │ │QualifiedTableName: test.t1 │| -|│PushDownLimitToEachDevice: false │ │OutputSymbols: [time, device_id, type, speed]│| -|│RegionId: 2 │ │DeviceNumber: 1 │| -|└─────────────────────────────────────────────┘ │ScanOrder: ASC │| -| │PushDownOffset: 0 │| -| │PushDownLimit: 0 │| -| │PushDownLimitToEachDevice: false │| -| │RegionId: 1 │| -| └─────────────────────────────────────────────┘| -+-----------------------------------------------------------------------------------------------+ -``` - - -## 2. Explain Analyze 语句 - -#### 语法 - -```SQL -EXPLAIN ANALYZE [VERBOSE] -``` - -其中 SELECT_STATEMENT 对应需要分析的查询语句;VERBOSE为打印详细分析结果,不填写VERBOSE时EXPLAIN ANALYZE将会省略部分信息。 - -#### 含义 - -Explain Analyze 是 IOTDB 查询引擎自带的性能分析 SQL,与 Explain 不同,它会执行对应的查询计划并统计执行信息,可以用于追踪一条查询的具体性能分布,用于对资源进行观察,进行性能调优与异常分析。 - -在 EXPLAIN ANALYZE 的结果集中,会包含如下信息: - -![explain-analyze-1.png](/img/explain-analyze-1.png) - -其中: - -- QueryStatistics 包含查询层面进的统计信息,主要包含各规划阶段耗时,查询分片数量等信息。 - - Analyze Cost: SQL 分析阶段的耗时(包含 FetchPartitionCost 和 FetchSchemaCost) - - Fetch Partition Cost:拉取分区表的耗时 - - Fetch Schema Cost:拉取元数据以及权限校验的耗时 - - Logical Plan Cost:构建逻辑计划的耗时 - - Logical Optimization Cost: 逻辑计划优化的耗时 - - Distribution Plan Cost:构建分布式计划的耗时 - - Fragment Instance Count:总的查询分片的数量,每个查询分片的信息会挨个输出 -- FragmentInstance 是 IoTDB 一个查询分片的封装,每一个查询分片都会在结果集中输出一份分片的执行信息,主要包含 FragmentStatistics 和算子信息。FragmentStastistics 包含 Fragment 的统计信息,包括总实际耗时(墙上时间),所涉及到的 TsFile,调度信息等情况。在一个 Fragment 的信息输出同时会以节点树层级的方式展示该Fragment 下计划节点的统计信息,主要包括:CPU运行时间、输出的数据行数、指定接口被调用的次数、所占用的内存、节点专属的定制信息。 - - Total Wall Time: 该分片从开始执行到执行结束的物理时间 - - Cost of initDataQuerySource:构建查询文件列表 - - Seq File(unclosed): 未封口(memtable)的顺序文件数量, Seq File(closed): 封口的顺序文件数量 - - UnSeq File(unclosed): 未封口(memtable)的乱序文件数量, UnSeq File(closed): 未封口的乱序文件数量 - - ready queued time: 查询分片的所有task在 ready queue 中的总时长(task 未阻塞,但没有查询执行线程资源时,会被放入ready queue),blocked queued time: 查询分片的所有task在 bloced queue 中的总时长(task 因为某些资源,如内存,或者上游数据未发送过来,则会被放入blocked queue) - - BloomFilter 相关(用于判断某个序列在tsfile中是否存在,存储于tsfile尾部) - - loadBloomFilterFromCacheCount: 命中BloomFilterCache的次数 - - loadBloomFilterFromDiskCount: 从磁盘中读取的次数 - - loadBloomFilterActualIOSize: 从磁盘中读取BloomFilter时耗费的磁盘IO(单位为bytes) - - loadBloomFilterTime: 读取BloomFilter + 计算序列是否存在的总耗时(单位为ms) - - TimeSeriesMetadata 相关(序列在tsfile中的索引信息,一个序列在一个tsfile中只会存储一个) - - loadTimeSeriesMetadataDiskSeqCount: 从封口的顺序文件里加载出的TimeSeriesMetadata数量 - - 大部分情况下等于Seq File(closed),但如果有limit等算子,可能实际加载的数量会小于Seq File(closed) - - loadTimeSeriesMetadataDiskUnSeqCount: 从封口的乱序文件里加载出的TimeSeriesMetadata数量 - - 大部分情况下等于UnSeq File(closed),但如果有limit等算子,可能实际加载的数量会小于UnSeq File(closed) - - loadTimeSeriesMetadataDiskSeqTime: 从封口的顺序文件里加载TimeSeriesMetadata的耗时 - - 并不是所有的 TimeSeriesMetadata 加载都涉及磁盘 IO,有可能会命中TimeSeriesMetadataCache,直接从缓存中读取,但输出信息里并没有分开统计这两者的耗时 - - loadTimeSeriesMetadataDiskUnSeqTime: 从未封口的顺序文件里加载TimeSeriesMetadata的耗时 - - loadTimeSeriesMetadataFromCacheCount: 命中TimeSeriesMetadataCache的次数,注意这里对于对齐设备来讲,每个分量(包括time列,都会去单独请求Cache,所以对于对齐设备来讲,(loadTimeSeriesMetadataFromCacheCount + loadTimeSeriesMetadataFromDiskCount)= tsfile number * subSensor number(包括time列) - - loadTimeSeriesMetadataFromDiskCount: 从磁盘中读取TimeSeriesMetadata的次数,一次读取会把该设备下查询所有涉及到的分量都读出来缓存在TimeSeriesMetadataCache中 - - loadTimeSeriesMetadataActualIOSize: 从磁盘中读取TimeSeriesMetadata时耗费的磁盘IO(单位为bytes) - - Mods 文件相关 - - TimeSeriesMetadataModificationTime: 读取mods文件花费的时间 - - Chunk 相关 - - constructAlignedChunkReadersDiskCount: 读取已封口 tsfile 中总的 Chunk 数量 - - constructAlignedChunkReadersDiskTime: 读取已封口 tsfile 中 Chunk 的总耗时(包含磁盘IO和解压缩) - - pageReadersDecodeAlignedDiskCount: 解编码已封口 tsfile 中总的 page 数量 - - pageReadersDecodeAlignedDiskTime: 解编码已封口 tsfile 中 page 的总耗时 - - loadChunkFromCacheCount: 命中ChunkCache的次数,注意这里对于对齐设备来讲,每个分量(包括time列,都会去单独请求Cache,所以对于对齐设备来讲,(loadChunkFromCacheCount + loadChunkFromDiskCount)= tsfile number * subSensor number(包括time列)* avg chunk number in each tsfile - - loadChunkFromDiskCount: 从磁盘中读取Chunk的次数,一次只会读取一个分量,并缓存在ChunkCache中 - - loadChunkActualIOSize: 从磁盘中读取Chunk时耗费的磁盘IO(单位为bytes) - - 自V2.0.9起,在 FragmentInstance 中,将增加如下信息: - - OutputPlanNodeId:表示sink节点对应的下游接收数据的节点,仅在sink节点中出现 - - sizeInBytes:表示exchange节点中接收到的TsBlock的字节(仅计算数据占用大小),仅在exchange节点中出现 - - tableScan节点过滤数据相关字段,仅在filter下推到tableScan中才有效,且仅在tableScan节点中出现: - - TimeSeriesIndexFilteredRows : 通过序列元数据(TimeseriesMetadata)过滤掉的数据行数 - - ChunkIndexFilteredRows: 通过列块元数据(ChunkMetadata)过滤掉的数据行数 - - PageIndexFilteredRows:通过页头内部(PageHeader)过滤掉的数据行数 - - RowScanFilteredRows:一行行检查数据时候被过滤掉的行数(仅在带有verbose时才会展现) - - -##### 特别说明 - -查询超时场景使用 Explain Analyze 语句: - -Explain Analyze 本身是一种特殊的查询,所以当执行超时的时候,Explain Analyze 语句也无法正常返回结果。为了在查询超时的情况下也可以通过分析结果排查超时原因,Explain Analyze 提供了定时日志机制(无需用户配置),每经过一定的时间间隔会将 Explain Analyze 的当前结果以文本的形式输出到专门的日志中。当查询超时时,用户可以前往logs/log_explain_analyze.log中查看对应的日志进行排查。 -日志的时间间隔基于查询的超时时间进行计算,可以保证在超时的情况下至少会有两次的结果记录。 - -##### 示例 - -下面是Explain Analyze的一个例子: - -```SQL --- 创建数据库 -create database test; - --- 创建表 -use test; -create table t1 (device_id STRING ID, type STRING ATTRIBUTE, speed float); - --- 插入数据 -insert into t1(device_id, type, speed) values('car_1', 'Model Y', 120.0); -insert into t1(device_id, type, speed) values('car_2', 'Model 3', 100.0); - --- 执行 explain analyze 语句 -explain analyze verbose select * from t1; -``` - -得到输出如下: - -```SQL -+-----------------------------------------------------------------------------------------------+ -| Explain Analyze| -+-----------------------------------------------------------------------------------------------+ -|Analyze Cost: 38.860 ms | -|Fetch Partition Cost: 9.888 ms | -|Fetch Schema Cost: 54.046 ms | -|Logical Plan Cost: 10.102 ms | -|Logical Optimization Cost: 17.396 ms | -|Distribution Plan Cost: 2.508 ms | -|Dispatch Cost: 22.126 ms | -|Fragment Instances Count: 2 | -| | -|FRAGMENT-INSTANCE[Id: 20241127_090849_00009_1.2.0][IP: 0.0.0.0][DataRegion: 2][State: FINISHED]| -| Total Wall Time: 18 ms | -| Cost of initDataQuerySource: 6.153 ms | -| Seq File(unclosed): 1, Seq File(closed): 0 | -| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | -| ready queued time: 0.164 ms, blocked queued time: 0.342 ms | -| Query Statistics: | -| loadBloomFilterFromCacheCount: 0 | -| loadBloomFilterFromDiskCount: 0 | -| loadBloomFilterActualIOSize: 0 | -| loadBloomFilterTime: 0.000 | -| loadTimeSeriesMetadataAlignedMemSeqCount: 1 | -| loadTimeSeriesMetadataAlignedMemSeqTime: 0.246 | -| loadTimeSeriesMetadataFromCacheCount: 0 | -| loadTimeSeriesMetadataFromDiskCount: 0 | -| loadTimeSeriesMetadataActualIOSize: 0 | -| constructAlignedChunkReadersMemCount: 1 | -| constructAlignedChunkReadersMemTime: 0.294 | -| loadChunkFromCacheCount: 0 | -| loadChunkFromDiskCount: 0 | -| loadChunkActualIOSize: 0 | -| pageReadersDecodeAlignedMemCount: 1 | -| pageReadersDecodeAlignedMemTime: 0.047 | -| [PlanNodeId 43]: IdentitySinkNode(IdentitySinkOperator) | -| CPU Time: 5.523 ms | -| output: 2 rows | -| HasNext() Called Count: 6 | -| Next() Called Count: 5 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 31]: CollectNode(CollectOperator) | -| CPU Time: 5.512 ms | -| output: 2 rows | -| HasNext() Called Count: 6 | -| Next() Called Count: 5 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 29]: TableScanNode(TableScanOperator) | -| CPU Time: 5.439 ms | -| output: 1 rows | -| HasNext() Called Count: 3 -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| DeviceNumber: 1 | -| CurrentDeviceIndex: 0 | -| [PlanNodeId 40]: ExchangeNode(ExchangeOperator) | -| CPU Time: 0.053 ms | -| output: 1 rows | -| HasNext() Called Count: 2 | -| Next() Called Count: 1 | -| Estimated Memory Size: : 131072 | -| | -|FRAGMENT-INSTANCE[Id: 20241127_090849_00009_1.3.0][IP: 0.0.0.0][DataRegion: 1][State: FINISHED]| -| Total Wall Time: 13 ms | -| Cost of initDataQuerySource: 5.725 ms | -| Seq File(unclosed): 1, Seq File(closed): 0 | -| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | -| ready queued time: 0.118 ms, blocked queued time: 5.844 ms | -| Query Statistics: | -| loadBloomFilterFromCacheCount: 0 | -| loadBloomFilterFromDiskCount: 0 | -| loadBloomFilterActualIOSize: 0 | -| loadBloomFilterTime: 0.000 | -| loadTimeSeriesMetadataAlignedMemSeqCount: 1 | -| loadTimeSeriesMetadataAlignedMemSeqTime: 0.004 | -| loadTimeSeriesMetadataFromCacheCount: 0 | -| loadTimeSeriesMetadataFromDiskCount: 0 | -| loadTimeSeriesMetadataActualIOSize: 0 | -| constructAlignedChunkReadersMemCount: 1 | -| constructAlignedChunkReadersMemTime: 0.001 | -| loadChunkFromCacheCount: 0 | -| loadChunkFromDiskCount: 0 | -| loadChunkActualIOSize: 0 | -| pageReadersDecodeAlignedMemCount: 1 | -| pageReadersDecodeAlignedMemTime: 0.007 | -| [PlanNodeId 42]: IdentitySinkNode(IdentitySinkOperator) | -| CPU Time: 0.270 ms | -| output: 1 rows | -| HasNext() Called Count: 3 | -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| [PlanNodeId 30]: TableScanNode(TableScanOperator) | -| CPU Time: 0.250 ms | -| output: 1 rows | -| HasNext() Called Count: 3 | -| Next() Called Count: 2 | -| Estimated Memory Size: : 327680 | -| DeviceNumber: 1 | -| CurrentDeviceIndex: 0 | -+-----------------------------------------------------------------------------------------------+ -``` - -## 3. 常见问题 - -#### 1. WALL TIME(墙上时间)和 CPU TIME(CPU时间)的区别? - -CPU 时间也称为处理器时间或处理器使用时间,指的是程序在执行过程中实际占用 CPU 进行计算的时间,显示的是程序实际消耗的处理器资源。 - -墙上时间也称为实际时间或物理时间,指的是从程序开始执行到程序结束的总时间,包括了所有等待时间。。 - -1. WALL TIME < CPU TIME 的场景:比如一个查询分片最后被调度器使用两个线程并行执行,真实物理世界上是 10s 过去了,但两个线程,可能一直占了两个 cpu 核跑了 10s,那 cpu time 就是 20s,wall time就是 10s -2. WALL TIME > CPU TIME 的场景:因为系统内可能会存在多个查询并行执行,但查询的执行线程数和内存是固定的, - 1. 所以当查询分片被某些资源阻塞住时(比如没有足够的内存进行数据传输、或等待上游数据)就会放入Blocked Queue,此时查询分片并不会占用 CPU TIME,但WALL TIME(真实物理时间的时间)是在向前流逝的 - 2. 或者当查询线程资源不足时,比如当前共有16个查询线程,但系统内并发有20个查询分片,即使所有查询都没有被阻塞,也只会同时并行运行16个查询分片,另外四个会被放入 READY QUEUE,等待被调度执行,此时查询分片并不会占用 CPU TIME,但WALL TIME(真实物理时间的时间)是在向前流逝的 - - -#### 2. Explain Analyze 是否有额外开销,测出的耗时是否与查询真实执行时有差别? - -几乎没有,因为 explain analyze operator 是单独的线程执行,收集原查询的统计信息,且这些统计信息,即使不explain analyze,原来的查询也会生成,只是没有人去取。并且 explain analyze 是纯 next 遍历结果集,不会打印,所以与原来查询真实执行时的耗时不会有显著差别。 - -#### 3. IO 耗时主要关注几个指标? - -涉及 IO 耗时的指标主要有:loadBloomFilterActualIOSize, loadBloomFilterTime, loadTimeSeriesMetadataAlignedDisk[Seq/Unseq]Time, loadTimeSeriesMetadataActualIOSize, alignedTimeSeriesMetadataModificationTime, constructAlignedChunkReadersDiskTime, loadChunkActualIOSize,各指标的具体含义见上文。 -TimeSeriesMetadata 的加载分别统计了顺序和乱序文件,但 Chunk 的读取暂时未分开统计,但顺乱序比例可以通过TimeseriesMetadata 顺乱序的比例计算出来。 - - -#### 4. 乱序数据对查询性能的影响能否有一些指标展示出来? - -乱序数据产生的影响主要有两个: - -1. 需要在内存中多做一个归并排序(一般认为这个耗时是比较短的,毕竟是纯内存的 cpu 操作) -2. 乱序数据会产生数据块间的时间范围重叠,导致统计信息无法使用 - 1. 无法利用统计信息直接 skip 掉整个不满足值过滤要求的 chunk - 1. 一般用户的查询都是只包含时间过滤条件,则不会有影响 - 2. 无法利用统计信息直接计算出聚合值,无需读取数据 - -单独对于乱序数据的性能影响,目前并没有有效的观测手段,除非就是在有乱序数据的时候,执行一遍查询耗时,然后等乱序合完了,再执行一遍,才能对比出来。 - -因为即使乱序这部分数据进了顺序,也是需要 IO、加压缩、decode,这个耗时少不了,不会因为乱序数据被合并进乱序了,就减少了。 - -#### 5. 执行 explain analyze 时,查询超时后,为什么结果没有输出在 log_explain_analyze.log 中? - -升级时,只替换了 lib 包,没有替换 conf/logback-datanode.xml,需要替换一下 conf/logback-datanode.xml,然后不需要重启(该文件内容可以被热加载),大约等待 1 分钟后,重新执行 explain analyze verbose。 diff --git a/src/zh/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis_apache.md b/src/zh/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis_apache.md new file mode 100644 index 000000000..f5fe38525 --- /dev/null +++ b/src/zh/UserGuide/latest-Table/User-Manual/Query-Performance-Analysis_apache.md @@ -0,0 +1,1829 @@ + +# 查询性能分析 + +## 1. 概述 + +查询分析用于帮助用户理解查询的执行机制和性能瓶颈,从而进行查询优化和性能调优。IoTDB 表模型提供两类查询分析语句: + +- `EXPLAIN`:预览查询 SQL 的执行计划,展示 IoTDB 如何组织数据检索和处理。 + +- `EXPLAIN ANALYZE`:在 `EXPLAIN` 基础上真实执行查询,并展示查询执行过程中的时间、资源消耗和算子统计信息。 + +`EXPLAIN` 和 `EXPLAIN ANALYZE` 默认输出面向人工阅读,适合在 CLI 中直接查看。对于 Web Console、CI 回归测试、自动化性能诊断系统等需要稳定解析查询计划或执行统计的场景,可以使用 JSON 输出格式获取结构化结果。JSON 格式自 V2.0.11 起支持。 + +### 1.1 查询分析方式对比 + +与 Arthas 抽样等排查手段相比,`EXPLAIN ANALYZE` 无需部署额外组件,可以针对单条 SQL 进行分布式追踪,更适合定位具体查询的性能问题。 + +|方法|安装难度|业务影响|功能范围| +|---|---|---|---| +|`EXPLAIN ANALYZE` 语句|低。无需安装额外组件,为 IoTDB 内置 SQL 语句|低。只会影响当前分析的单条查询,对线上其他负载无影响|支持分布式,可对单条 SQL 进行追踪| +|Arthas 抽样|中。需要安装 Java Arthas 工具|高。CPU 抽样可能会影响线上业务响应速度|不支持分布式,仅支持对数据库整体查询负载和耗时进行分析| + +## 2. EXPLAIN + +### 2.1 语法 + +`EXPLAIN` 命令用于查看 SQL 查询的分布式执行计划。执行计划以算子树的形式展示,描述 IoTDB 将如何执行查询。 + +```SQL +EXPLAIN [(FORMAT { GRAPHVIZ | JSON })] +``` + +其中: + +|参数|说明| +|---|---| +|`SELECT_STATEMENT`|需要分析的查询语句| +|`FORMAT GRAPHVIZ`|以默认图形文本形式输出分布式计划,适合人工阅读| +|`FORMAT JSON`|以 JSON 对象输出分布式计划,适合程序解析(自 V2.0.11 起支持)| + +`FORMAT` 后的格式名大小写不敏感,例如 `FORMAT json` 与 `FORMAT JSON` 等价。 + +### 2.2 默认 GRAPHVIZ 格式 + +`EXPLAIN` 默认格式为 `GRAPHVIZ`,执行后将得到 `distribution plan` 结果列。 + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 为例,以下两条语句等价: + +```SQL +EXPLAIN SELECT * FROM table1; +EXPLAIN (FORMAT GRAPHVIZ) SELECT * FROM table1; +``` + +执行如上语句后,输出如下:IoTDB 通过 `DeviceTableScanNode` 节点从不同数据分区读取数据,并通过 `Collect` 算子汇总后返回。 + +```SQL ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| distribution plan| ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | +| │OutputNode-4 │ | +| │OutputColumns-[time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time] │ | +| │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ | +| └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | +| │ | +| │ | +| ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ | +| │Collect-33 │ | +| │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ | +| └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ | +| ┌───────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────┐ | +| │ │ | +| ┌───────────┐ ┌───────────┐ | +| │Exchange-40│ │Exchange-41│ | +| └───────────┘ └───────────┘ | +| │ │ | +| │ │ | +|┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐| +|│DeviceTableScanNode-32 │ │DeviceTableScanNode-31 │| +|│QualifiedTableName: database1.table1 │ │QualifiedTableName: database1.table1 │| +|│OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│ │OutputSymbols: [time, region, plant_id, device_id, model_id, maintenance, temperature, humidity, status, arrival_time]│| +|│DeviceNumber: 4 │ │DeviceNumber: 2 │| +|│ScanOrder: ASC │ │ScanOrder: ASC │| +|│PushDownOffset: 0 │ │PushDownOffset: 0 │| +|│PushDownLimit: 0 │ │PushDownLimit: 0 │| +|│PushDownLimitToEachDevice: false │ │PushDownLimitToEachDevice: false │| +|│RegionId: 1 │ │RegionId: 2 │| +|└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘| ++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +``` + +### 2.3 JSON 格式 + +使用 `EXPLAIN (FORMAT JSON)` 可以返回分布式计划树的 JSON 表示。结果集为单列输出,JSON 格式下返回单行、单个 JSON 对象,便于 JDBC 客户端或自动化工具直接读取。 + +`EXPLAIN (FORMAT JSON)` 的顶层结构随查询类型变化: + +|场景|顶层结构|说明| +|---|---|---| +|普通查询|plan node JSON object|顶层直接是 `OutputNode` 等计划节点对象| +|包含 materialized CTE 的查询|wrapper JSON object|顶层包含 `cteQueries` 和 `mainQuery`,用于同时保留 CTE 子查询计划与主查询计划| + +每个 plan node 输出为一个 JSON object,基础字段如下: + +|字段|类型/出现位置|说明| +|---|---|---| +|`name`|string|节点类型与 plan node id 拼接后的展示名,例如 `OutputNode-4`| +|`id`|string|plan node id| +|`properties`|object|节点属性。仅当节点存在可展示属性时输出| +|`children`|array|子节点数组。仅当节点存在子节点时输出| + +常见 plan node 属性包括: + +|节点类型|主要字段/属性| +|---|---| +|`OutputNode`|`OutputColumns`、`OutputSymbols`| +|`TableScanNode` / `DeviceTableScanNode`|`QualifiedTableName`、`OutputSymbols`、`DeviceNumber`、`ScanOrder`、`TimePredicate`、`PushDownPredicate`、`PushDownOffset`、`PushDownLimit`、`PushDownLimitToEachDevice`、`RegionId`| +|`TreeDeviceViewScanNode`|除表扫描属性外,补充 `TreeDB`、`MeasurementToColumnName`| +|`AggregationNode`|`OutputSymbols`、`Aggregators`、`GroupingKeys`、`Streamable`、`PreGroupedSymbols`、`Step`| +|`FilterNode`|`Predicate`| +|`ProjectNode`|`OutputSymbols`、`Expressions`| +|`LimitNode` / `OffsetNode`|`Count`| +|`SortNode` / `MergeSortNode`|`OrderBy`| +|`JoinNode`|`JoinType`、`Criteria`、`OutputSymbols`| +|`UnionNode`|`OutputSymbols`| +|`ExplainAnalyzeNode`|`ChildPermittedOutputs`| + +当查询包含 materialized CTE 时,JSON 顶层不再直接是单个 plan node,而是包装为: + +```JSON +{ + "cteQueries": [ + { + "name": "cte1", + "plan": { + "name": "OutputNode-", + "id": "" + } + } + ], + "mainQuery": { + "name": "OutputNode-", + "id": "" + } +} +``` + +其中 `cteQueries[].plan` 与 `mainQuery` 均为 plan node JSON object。该结构用于同时保留 CTE 子查询计划与主查询计划。 + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 为例: + +```SQL +EXPLAIN (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "CollectNode-33", + "id": "33", + "children": [ + { + "name": "ExchangeNode-40", + "id": "40", + "children": [ + { + "name": "DeviceTableScanNode-32", + "id": "32", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-41", + "id": "41", + "children": [ + { + "name": "DeviceTableScanNode-31", + "id": "31", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] +} ++-----------------+ +``` + +## 3. EXPLAIN ANALYZE + +### 3.1 语法 + +`EXPLAIN ANALYZE` 是 IoTDB 查询引擎自带的性能分析 SQL。与 `EXPLAIN` 不同,它会真实执行对应查询计划并统计执行信息,可用于追踪一条查询的具体性能分布,辅助资源观察、性能调优和异常分析。 + +```SQL +EXPLAIN ANALYZE [VERBOSE] [(FORMAT { TEXT | JSON })] +``` + +其中: + +|参数|说明| +|---|---| +|`SELECT_STATEMENT`|需要分析的查询语句| +|`VERBOSE`|打印更详细的分析结果。不填写时会省略部分细粒度统计| +|`FORMAT TEXT`|以默认文本形式输出分析结果,适合人工阅读| +|`FORMAT JSON`|以 JSON 对象输出分析结果,适合程序解析(自 V2.0.11 起支持)| + +### 3.2 默认 TEXT 格式 + +`EXPLAIN ANALYZE` 默认格式为 `TEXT`,执行后将得到 `Explain Analyze` 结果列。结果由查询规划统计、fragment instance 统计、数据读取统计和算子树统计组成。 + +- **查询规划统计** + +`QueryStatistics` 包含查询层面的统计信息: + +|字段|类型/出现位置|说明| +|---|---|---| +|`Analyze Cost`|查询规划统计|SQL 分析阶段的耗时| +|`Fetch Partition Cost`|查询规划统计|拉取分区表的耗时| +|`Fetch Schema Cost`|查询规划统计|拉取元数据以及权限校验的耗时| +|`Logical Plan Cost`|查询规划统计|构建逻辑计划的耗时| +|`Logical Optimization Cost`|查询规划统计|逻辑计划优化的耗时| +|`Distribution Plan Cost`|查询规划统计|构建分布式计划的耗时| +|`Dispatch Cost`|查询规划统计|分发 fragment instance 的耗时| +|`Fragment Instances Count`|查询规划统计|总查询分片数量,每个查询分片的信息会依次输出| + +- **Fragment instance 统计** + +`FragmentInstance` 是 IoTDB 一个查询分片的封装。每个查询分片都会输出一份执行信息,主要包含 fragment 统计和算子信息。 + +fragment 统计包括: + +|字段|类型/出现位置|说明| +|---|---|---| +|`Total Wall Time`|fragment 统计|该分片从开始执行到执行结束的物理时间| +|`Cost of initDataQuerySource`|fragment 统计|构建查询文件列表的耗时| +|`Seq File(unclosed)`|fragment 统计|未封口的顺序文件数量| +|`Seq File(closed)`|fragment 统计|已封口的顺序文件数量| +|`UnSeq File(unclosed)`|fragment 统计|未封口的乱序文件数量| +|`UnSeq File(closed)`|fragment 统计|已封口的乱序文件数量| +|`ready queued time`|fragment 统计|查询分片所有 task 在 ready queue 中的总时长| +|`blocked queued time`|fragment 统计|查询分片所有 task 在 blocked queue 中的总时长| + +- **数据读取统计** + +`Query Statistics` 明细字段包括: + +|字段|类型/出现位置|说明| +|---|---|---| +|`loadBloomFilterFromCacheCount`|BloomFilter|命中 BloomFilterCache 的次数| +|`loadBloomFilterFromDiskCount`|BloomFilter|从磁盘读取 BloomFilter 的次数| +|`loadBloomFilterActualIOSize`|BloomFilter|从磁盘读取 BloomFilter 时产生的磁盘 IO,单位为 bytes| +|`loadBloomFilterTime`|BloomFilter|读取 BloomFilter 并计算序列是否存在的总耗时,单位为 ms| +|`loadTimeSeriesMetadataDiskSeqCount`|TimeSeriesMetadata|从已封口顺序文件中加载的 TimeSeriesMetadata 数量| +|`loadTimeSeriesMetadataDiskUnSeqCount`|TimeSeriesMetadata|从已封口乱序文件中加载的 TimeSeriesMetadata 数量| +|`loadTimeSeriesMetadataDiskSeqTime`|TimeSeriesMetadata|从已封口顺序文件中加载 TimeSeriesMetadata 的耗时| +|`loadTimeSeriesMetadataDiskUnSeqTime`|TimeSeriesMetadata|从已封口乱序文件中加载 TimeSeriesMetadata 的耗时| +|`loadTimeSeriesMetadataFromCacheCount`|TimeSeriesMetadata|命中 TimeSeriesMetadataCache 的次数| +|`loadTimeSeriesMetadataFromDiskCount`|TimeSeriesMetadata|从磁盘读取 TimeSeriesMetadata 的次数| +|`loadTimeSeriesMetadataActualIOSize`|TimeSeriesMetadata|从磁盘读取 TimeSeriesMetadata 时产生的磁盘 IO,单位为 bytes| +|`TimeSeriesMetadataModificationTime`|Mods 文件|读取 mods 文件的耗时| +|`constructAlignedChunkReadersDiskCount`|Chunk|构造 ChunkReader 的次数| +|`constructAlignedChunkReadersDiskTime`|Chunk|构造 ChunkReader 的总耗时,包含磁盘 IO 和解压缩| +|`pageReadersDecodeAlignedDiskCount`|Chunk|解码 page 的数量| +|`pageReadersDecodeAlignedDiskTime`|Chunk|解码 page 的总耗时| +|`loadChunkFromCacheCount`|Chunk|命中 ChunkCache 的次数| +|`loadChunkFromDiskCount`|Chunk|从磁盘读取 Chunk 的次数| +|`loadChunkActualIOSize`|Chunk|从磁盘读取 Chunk 时产生的磁盘 IO,单位为 bytes| + +- **算子树统计** + +算子树统计包括: + +|字段|类型/出现位置|说明| +|---|---|---| +|`CPU Time`|算子树统计|当前算子的 CPU 执行耗时| +|`output`|算子树统计|当前算子的输出行数| +|`HasNext() Called Count`|算子树统计|当前算子 `HasNext()` 接口被调用的次数| +|`Next() Called Count`|算子树统计|当前算子 `Next()` 接口被调用的次数| +|`Estimated Memory Size`|算子树统计|当前算子的估算内存占用| +|节点专属统计信息|算子树统计|不同算子输出的定制统计字段,例如 `DeviceNumber`、`CurrentDeviceIndex`、`OutputPlanNodeId`、`size_in_bytes` 等| + +常见节点专属统计字段包括: + +|字段|类型/出现位置|说明| +|---|---|---| +|`DeviceNumber`|`TableScan` 相关节点|当前 table scan 涉及的设备数量| +|`CurrentDeviceIndex`|`TableScan` 相关节点|当前正在扫描的设备索引| +|`OutputPlanNodeId`|sink 节点|sink 节点对应的下游接收数据节点| +|`size_in_bytes`|exchange 节点|exchange 节点接收到的 TsBlock 字节数,仅计算数据占用大小| +|`TimeSeriesIndexFilteredRows`|filter 下推到 table scan 时的 table scan 节点|通过序列元数据过滤掉的数据行数| +|`ChunkIndexFilteredRows`|filter 下推到 table scan 时的 table scan 节点|通过列块元数据过滤掉的数据行数| +|`PageIndexFilteredRows`|filter 下推到 table scan 时的 table scan 节点|通过页头内部统计信息过滤掉的数据行数| +|`RowScanFilteredRows`|filter 下推到 table scan 且使用 `VERBOSE` 时的 table scan 节点|逐行检查数据时被过滤掉的行数| + +实际输出字段会随查询类型、涉及的算子、是否使用 `VERBOSE` 以及是否触发过滤下推而变化。未触发对应逻辑时,相关字段可能不会出现在结果中。 + +- **文本输出示例** + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 为例,以下两条语句等价: + +```SQL +EXPLAIN ANALYZE SELECT * FROM table1; +EXPLAIN ANALYZE (FORMAT TEXT) SELECT * FROM table1; +``` + +```SQL ++-------------------------------------------------------------------------------------------------------------------------+ +| Explain Analyze| ++-------------------------------------------------------------------------------------------------------------------------+ +|Analyze Cost: 3.862 ms | +|Fetch Partition Cost: 0.785 ms | +|Fetch Schema Cost: 5.473 ms | +|Logical Plan Cost: 37.350 ms | +|Logical Optimization Cost: 4.340 ms | +|Distribution Plan Cost: 1.287 ms | +|Dispatch Cost: 8.711 ms | +|Fragment Instances Count: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.2.0][IP: 127.0.0.1:10730][DataRegion: virtual_data_region][State: FINISHED]| +| Total Wall Time: 64 ms | +| Cost of initDataQuerySource: 0.000 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.428 ms, blocked queued time: 11.410 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 69]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 28.691 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| DownStreamPlanNodeId: 66 | +| [PlanNodeId 55]: CollectNode(CollectOperator) | +| CPU Time: 28.656 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| [PlanNodeId 64]: ExchangeNode(ExchangeOperator) | +| CPU Time: 13.310 ms | +| output: 12 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 3264 | +| [PlanNodeId 65]: ExchangeNode(ExchangeOperator) | +| CPU Time: 15.286 ms | +| output: 6 rows | +| HasNext() Called Count: 3 | +| Next() Called Count: 2 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 1632 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.3.0][IP: 127.0.0.1:10730][DataRegion: 1][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 7.652 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 2, UnSeq File(closed): 0 | +| ready queued time: 0.089 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 67]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 2.007 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 64 | +| [PlanNodeId 54]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 1.981 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 4 | +| CurrentDeviceIndex: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064147_00096_1.4.0][IP: 127.0.0.1:10730][DataRegion: 2][State: FINISHED] | +| Total Wall Time: 48 ms | +| Cost of initDataQuerySource: 0.933 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 1, UnSeq File(closed): 0 | +| ready queued time: 0.141 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| [PlanNodeId 68]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 1.846 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 65 | +| [PlanNodeId 53]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 1.755 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 2 | +| CurrentDeviceIndex: 1 | ++-------------------------------------------------------------------------------------------------------------------------+ +``` + +- **VERBOSE 输出示例** + +使用 `VERBOSE` 可以查看更细粒度的数据读取和过滤统计: + +```SQL +EXPLAIN ANALYZE VERBOSE SELECT * FROM table1; +EXPLAIN ANALYZE VERBOSE (FORMAT TEXT) SELECT * FROM table1; +``` + +```YAML ++-------------------------------------------------------------------------------------------------------------------------+ +| Explain Analyze| ++-------------------------------------------------------------------------------------------------------------------------+ +|Analyze Cost: 1.016 ms | +|Fetch Partition Cost: 0.555 ms | +|Fetch Schema Cost: 3.789 ms | +|Logical Plan Cost: 0.491 ms | +|Logical Optimization Cost: 2.625 ms | +|Distribution Plan Cost: 0.393 ms | +|Dispatch Cost: 37.041 ms | +|Fragment Instances Count: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.2.0][IP: 127.0.0.1:10730][DataRegion: virtual_data_region][State: FINISHED]| +| Total Wall Time: 43 ms | +| Cost of initDataQuerySource: 0.000 ms | +| Seq File(unclosed): 0, Seq File(closed): 0 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.248 ms, blocked queued time: 23.757 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 0 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.000 | +| loadTimeSeriesMetadataFromCacheCount: 0 | +| loadTimeSeriesMetadataFromDiskCount: 0 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| loadChunkFromCacheCount: 0 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 69]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 0.988 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| DownStreamPlanNodeId: 66 | +| [PlanNodeId 55]: CollectNode(CollectOperator) | +| CPU Time: 0.954 ms | +| output: 18 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 131072 | +| [PlanNodeId 64]: ExchangeNode(ExchangeOperator) | +| CPU Time: 0.569 ms | +| output: 12 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 3264 | +| [PlanNodeId 65]: ExchangeNode(ExchangeOperator) | +| CPU Time: 0.354 ms | +| output: 6 rows | +| HasNext() Called Count: 3 | +| Next() Called Count: 2 | +| Estimated Memory Size: 131072 | +| size_in_bytes: 1632 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.3.0][IP: 127.0.0.1:10730][DataRegion: 1][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 0.224 ms | +| Seq File(unclosed): 0, Seq File(closed): 2 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.025 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 2 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.018 | +| loadTimeSeriesMetadataAlignedDiskSeqCount: 4 | +| loadTimeSeriesMetadataAlignedDiskSeqTime: 0.189 | +| loadTimeSeriesMetadataFromCacheCount: 18 | +| loadTimeSeriesMetadataFromDiskCount: 2 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| alignedTimeSeriesMetadataModificationTime: 0.026 | +| constructAlignedChunkReadersDiskCount: 4 | +| constructAlignedChunkReadersDiskTime: 3.251 | +| loadChunkFromCacheCount: 18 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| pageReadersDecodeAlignedDiskCount: 4 | +| pageReadersDecodeAlignedDiskTime: 0.210 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 67]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 5.150 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 64 | +| [PlanNodeId 54]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 5.136 ms | +| output: 12 rows | +| HasNext() Called Count: 9 | +| Next() Called Count: 8 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 4 | +| CurrentDeviceIndex: 3 | +| | +|FRAGMENT-INSTANCE[Id: 20260722_064913_00105_1.4.0][IP: 127.0.0.1:10730][DataRegion: 2][State: FINISHED] | +| Total Wall Time: 32 ms | +| Cost of initDataQuerySource: 0.397 ms | +| Seq File(unclosed): 0, Seq File(closed): 1 | +| UnSeq File(unclosed): 0, UnSeq File(closed): 0 | +| ready queued time: 0.075 ms, blocked queued time: 0.000 ms | +| Query Statistics: | +| loadBloomFilterFromCacheCount: 0 | +| loadBloomFilterFromDiskCount: 0 | +| loadBloomFilterActualIOSize: 0 | +| loadBloomFilterTime: 0.000 | +| loadTimeSeriesMetadataAlignedDiskSeqCount: 2 | +| loadTimeSeriesMetadataAlignedDiskSeqTime: 0.313 | +| loadTimeSeriesMetadataFromCacheCount: 10 | +| loadTimeSeriesMetadataFromDiskCount: 0 | +| loadTimeSeriesMetadataActualIOSize: 0 | +| alignedTimeSeriesMetadataModificationTime: 0.046 | +| constructAlignedChunkReadersDiskCount: 2 | +| constructAlignedChunkReadersDiskTime: 2.307 | +| loadChunkFromCacheCount: 10 | +| loadChunkFromDiskCount: 0 | +| loadChunkActualIOSize: 0 | +| pageReadersDecodeAlignedDiskCount: 2 | +| pageReadersDecodeAlignedDiskTime: 0.129 | +| timeSeriesIndexFilteredRows: 0 | +| chunkIndexFilteredRows: 0 | +| pageIndexFilteredRows: 0 | +| rowScanFilteredRows: 0 | +| [PlanNodeId 68]: IdentitySinkNode(IdentitySinkOperator) | +| CPU Time: 4.187 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DownStreamPlanNodeId: 65 | +| [PlanNodeId 53]: DeviceTableScanNode(TableScanOperator) | +| CPU Time: 4.172 ms | +| output: 6 rows | +| HasNext() Called Count: 5 | +| Next() Called Count: 4 | +| Estimated Memory Size: 327680 | +| DeviceNumber: 2 | +| CurrentDeviceIndex: 1 | ++-------------------------------------------------------------------------------------------------------------------------+ +``` + +### 3.3 JSON 格式 + +使用 `EXPLAIN ANALYZE (FORMAT JSON)` 可以返回计划阶段耗时、fragment instance 统计、operator 统计等结构化信息。JSON 格式自 V2.0.11 起支持。 + +#### 3.3.1 默认 JSON 输出 + +```SQL +EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM t1; +``` + +顶层 JSON 结构如下: + +|字段|类型/出现位置|说明| +|---|---|---| +|`planStatistics`|object|查询分析、分区获取、schema 获取、逻辑规划、逻辑优化、分布式规划、dispatch 等阶段耗时| +|`fragmentInstancesCount`|number|返回统计信息的 fragment instance 数量| +|`fragmentInstances`|array|各 fragment instance 的执行统计| + +`planStatistics` 主要字段如下: + +|字段|类型/出现位置|说明| +|---|---|---| +|`analyzeCostMs`|planStatistics|SQL 分析耗时,单位 ms| +|`fetchPartitionCostMs`|planStatistics|获取分区信息耗时,单位 ms| +|`fetchSchemaCostMs`|planStatistics|获取 schema 信息耗时,单位 ms| +|`logicalPlanCostMs`|planStatistics|逻辑计划生成耗时,单位 ms| +|`logicalOptimizationCostMs`|planStatistics|逻辑优化耗时,单位 ms| +|`distributionPlanCostMs`|planStatistics|分布式计划生成耗时,单位 ms| +|`dispatchCostMs`|planStatistics|fragment dispatch 耗时,单位 ms| + +`fragmentInstances[]` 主要字段如下: + +|字段|类型/出现位置|说明| +|---|---|---| +|`id`|fragmentInstances[]|fragment instance id| +|`ip`|fragmentInstances[]|执行该 fragment instance 的节点 IP| +|`dataRegion`|fragmentInstances[]|对应 DataRegion| +|`state`|fragmentInstances[]|fragment instance 最终状态| +|`totalWallTimeMs`|fragmentInstances[]|总 wall time,单位 ms| +|`initDataQuerySourceCostMs`|fragmentInstances[]|初始化数据源耗时,单位 ms| +|`initDataQuerySourceRetryCount`|fragmentInstances[]|初始化数据源重试次数。仅当大于 0 时输出| +|`seqFileUnclosed`、`seqFileClosed`、`unseqFileUnclosed`、`unseqFileClosed`|fragmentInstances[]|涉及的顺序和乱序文件数量| +|`readyQueuedTimeMs`、`blockQueuedTimeMs`|fragmentInstances[]|ready / block 队列等待时间,单位 ms| +|`queryStatistics`|fragmentInstances[]|扫描、过滤、chunk/page reader 等查询统计| +|`operators`|fragmentInstances[]|operator tree 及每个 operator 的执行统计| + +`operators` 为与 plan tree 对应的树状结构,主要字段如下: + +|字段|类型/出现位置|说明| +|---|---|---| +|`planNodeId`|operators|对应 plan node id| +|`nodeType`|operators|plan node 类型| +|`operatorType`|operators|实际执行 operator 类型| +|`count`|operators|operator 统计聚合次数。仅当存在时输出| +|`cpuTimeMs`|operators|operator CPU 执行耗时,单位 ms| +|`outputRows`|operators|输出行数| +|`hasNextCalledCount`、`nextCalledCount`|operators|operator 迭代调用次数| +|`estimatedMemorySize`|operators|估算内存占用。仅非 0 时输出| +|`specifiedInfo`|operators|operator 特有统计信息。仅非空时输出| +|`children`|operators|子 operator 数组| + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 为例: + +```SQL +EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 4.948, + "fetchPartitionCostMs": 2.104, + "fetchSchemaCostMs": 13.321, + "logicalPlanCostMs": 2.264, + "logicalOptimizationCostMs": 7.941, + "distributionPlanCostMs": 1.434, + "dispatchCostMs": 9.023 + }, + "fragmentInstancesCount": 3, + "fragmentInstances": [ + { + "id": "20260722_064302_00101_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "virtual_data_region", + "state": "FINISHED", + "totalWallTimeMs": 97, + "initDataQuerySourceCostMs": 0.0, + "seqFileUnclosed": 0, + "seqFileClosed": 0, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.858, + "blockQueuedTimeMs": 71.752, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "69", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 6.082, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "DownStreamPlanNodeId": "66" + }, + "children": [ + { + "planNodeId": "55", + "nodeType": "CollectNode", + "operatorType": "CollectOperator", + "cpuTimeMs": 6.065, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "children": [ + { + "planNodeId": "64", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 1.119, + "outputRows": 12, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "3264" + } + }, + { + "planNodeId": "65", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 4.909, + "outputRows": 6, + "hasNextCalledCount": 3, + "nextCalledCount": 2, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "1632" + } + } + ] + } + ] + } + }, + { + "id": "20260722_064302_00101_1.3.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 63, + "initDataQuerySourceCostMs": 0.338, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.273, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "67", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 57.338, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "64" + }, + "children": [ + { + "planNodeId": "54", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 57.248, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "4", + "CurrentDeviceIndex": "3" + } + } + ] + } + }, + { + "id": "20260722_064302_00101_1.4.0", + "ip": "127.0.0.1:10730", + "dataRegion": "2", + "state": "FINISHED", + "totalWallTimeMs": 79, + "initDataQuerySourceCostMs": 0.231, + "seqFileUnclosed": 0, + "seqFileClosed": 1, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.038, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "68", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 66.387, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "65" + }, + "children": [ + { + "planNodeId": "53", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 66.362, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "2", + "CurrentDeviceIndex": "1" + } + } + ] + } + } + ] +} ++---------------+ +``` + +#### 3.3.2 VERBOSE JSON 输出 + +当使用 `EXPLAIN ANALYZE VERBOSE (FORMAT JSON)` 时,顶层结构与非 verbose JSON 一致,但 `queryStatistics` 会在基础过滤行数外补充更细粒度统计,包括 bloom filter、time series metadata、chunk、page reader、modification 和实际 IO size 等信息。 + +`VERBOSE` 与非 `VERBOSE` JSON 输出的主要差异如下: + +|输出模式|顶层结构|`queryStatistics` 字段粒度|典型字段| +|---|---|---|---| +|`EXPLAIN ANALYZE (FORMAT JSON)`|`planStatistics`、`fragmentInstancesCount`、`fragmentInstances`|默认统计字段|`timeSeriesIndexFilteredRows`、`chunkIndexFilteredRows`、`pageIndexFilteredRows`| +|`EXPLAIN ANALYZE VERBOSE (FORMAT JSON)`|与非 `VERBOSE` 一致|更详细的扫描、读取和过滤统计|`loadBloomFilterFromCacheCount`、`loadTimeSeriesMetadataFromCacheCount`、`loadChunkFromDiskCount`、`pageReadersDecodeAlignedMemCount`、`rowScanFilteredRows`| + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 为例: + +```SQL +EXPLAIN ANALYZE VERBOSE (FORMAT JSON) SELECT * FROM table1; +``` + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 3.505, + "fetchPartitionCostMs": 2.003, + "fetchSchemaCostMs": 9.198, + "logicalPlanCostMs": 1.481, + "logicalOptimizationCostMs": 6.684, + "distributionPlanCostMs": 0.711, + "dispatchCostMs": 8.32 + }, + "fragmentInstancesCount": 3, + "fragmentInstances": [ + { + "id": "20260722_064502_00103_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "virtual_data_region", + "state": "FINISHED", + "totalWallTimeMs": 48, + "initDataQuerySourceCostMs": 0.0, + "seqFileUnclosed": 0, + "seqFileClosed": 0, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.209, + "blockQueuedTimeMs": 17.997, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 0, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.0, + "loadTimeSeriesMetadataFromCacheCount": 0, + "loadTimeSeriesMetadataFromDiskCount": 0, + "loadTimeSeriesMetadataActualIOSize": 0, + "loadChunkFromCacheCount": 0, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "69", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 26.368, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "DownStreamPlanNodeId": "66" + }, + "children": [ + { + "planNodeId": "55", + "nodeType": "CollectNode", + "operatorType": "CollectOperator", + "cpuTimeMs": 26.32, + "outputRows": 18, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 131072, + "children": [ + { + "planNodeId": "64", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 0.47, + "outputRows": 12, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "3264" + } + }, + { + "planNodeId": "65", + "nodeType": "ExchangeNode", + "operatorType": "ExchangeOperator", + "cpuTimeMs": 25.802, + "outputRows": 6, + "hasNextCalledCount": 3, + "nextCalledCount": 2, + "estimatedMemorySize": 131072, + "specifiedInfo": { + "size_in_bytes": "1632" + } + } + ] + } + ] + } + }, + { + "id": "20260722_064502_00103_1.3.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 21, + "initDataQuerySourceCostMs": 0.232, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.007, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 2, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.02, + "loadTimeSeriesMetadataAlignedDiskSeqCount": 4, + "loadTimeSeriesMetadataAlignedDiskSeqTimeMs": 0.483, + "loadTimeSeriesMetadataFromCacheCount": 18, + "loadTimeSeriesMetadataFromDiskCount": 2, + "loadTimeSeriesMetadataActualIOSize": 0, + "alignedTimeSeriesMetadataModificationTimeMs": 0.061, + "constructAlignedChunkReadersDiskCount": 4, + "constructAlignedChunkReadersDiskTimeMs": 2.693, + "loadChunkFromCacheCount": 18, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "pageReadersDecodeAlignedDiskCount": 4, + "pageReadersDecodeAlignedDiskTimeMs": 0.22, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "67", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 5.002, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "64" + }, + "children": [ + { + "planNodeId": "54", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 4.94, + "outputRows": 12, + "hasNextCalledCount": 9, + "nextCalledCount": 8, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "4", + "CurrentDeviceIndex": "3" + } + } + ] + } + }, + { + "id": "20260722_064502_00103_1.4.0", + "ip": "127.0.0.1:10730", + "dataRegion": "2", + "state": "FINISHED", + "totalWallTimeMs": 32, + "initDataQuerySourceCostMs": 0.253, + "seqFileUnclosed": 0, + "seqFileClosed": 1, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.027, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "loadBloomFilterFromCacheCount": 0, + "loadBloomFilterFromDiskCount": 0, + "loadBloomFilterActualIOSize": 0, + "loadBloomFilterTimeMs": 0.0, + "loadTimeSeriesMetadataAlignedDiskSeqCount": 2, + "loadTimeSeriesMetadataAlignedDiskSeqTimeMs": 0.188, + "loadTimeSeriesMetadataFromCacheCount": 10, + "loadTimeSeriesMetadataFromDiskCount": 0, + "loadTimeSeriesMetadataActualIOSize": 0, + "alignedTimeSeriesMetadataModificationTimeMs": 0.037, + "constructAlignedChunkReadersDiskCount": 2, + "constructAlignedChunkReadersDiskTimeMs": 1.807, + "loadChunkFromCacheCount": 10, + "loadChunkFromDiskCount": 0, + "loadChunkActualIOSize": 0, + "pageReadersDecodeAlignedDiskCount": 2, + "pageReadersDecodeAlignedDiskTimeMs": 0.62, + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0, + "rowScanFilteredRows": 0 + }, + "operators": { + "planNodeId": "68", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 3.652, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "65" + }, + "children": [ + { + "planNodeId": "53", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 3.633, + "outputRows": 6, + "hasNextCalledCount": 5, + "nextCalledCount": 4, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "2", + "CurrentDeviceIndex": "1" + } + } + ] + } + } + ] +} +``` + +## 4. 场景示例 + +### 4.1 CTE 查询计划 + +当 `EXPLAIN (FORMAT JSON)` 分析包含 materialized CTE 的查询时,输出会使用 `cteQueries` \+ `mainQuery` 的包装结构。 + +以[示例数据](../Reference/Sample-Data.md)中的 `table1` 和 `table2` 为例: + +```SQL +EXPLAIN (FORMAT JSON) +WITH cte1 AS MATERIALIZED (SELECT * FROM table2) +SELECT * FROM table1 +WHERE table1.device_id IN (SELECT device_id FROM cte1); +``` + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "cteQueries": [ + { + "name": "cte1", + "plan": { + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "CollectNode-33", + "id": "33", + "children": [ + { + "name": "ExchangeNode-40", + "id": "40", + "children": [ + { + "name": "DeviceTableScanNode-32", + "id": "32", + "properties": { + "QualifiedTableName": "database1.table2", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-41", + "id": "41", + "children": [ + { + "name": "DeviceTableScanNode-31", + "id": "31", + "properties": { + "QualifiedTableName": "database1.table2", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] + } + } + ], + "mainQuery": { + "name": "OutputNode-12", + "id": "12", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "ProjectNode-35", + "id": "35", + "properties": { + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "Expressions": [ + "\"time\"", + "\"region\"", + "\"plant_id\"", + "\"device_id\"", + "\"model_id\"", + "\"maintenance\"", + "\"temperature\"", + "\"humidity\"", + "\"status\"", + "\"arrival_time\"" + ] + }, + "children": [ + { + "name": "FilterNode-138", + "id": "138", + "properties": { + "Predicate": "\"expr\"" + }, + "children": [ + { + "name": "SemiJoinNode-109", + "id": "109", + "children": [ + { + "name": "ExchangeNode-218", + "id": "218", + "children": [ + { + "name": "MergeSortNode-197", + "id": "197", + "properties": { + "OrderBy": "{orderBy\u003d[device_id], orderings\u003d{device_id\u003dASC NULLS LAST}}" + }, + "children": [ + { + "name": "ExchangeNode-216", + "id": "216", + "children": [ + { + "name": "DeviceTableScanNode-196", + "id": "196", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "true", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-217", + "id": "217", + "children": [ + { + "name": "DeviceTableScanNode-195", + "id": "195", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "true", + "RegionId": "2" + } + } + ] + } + ] + } + ] + }, + { + "name": "ExchangeNode-219", + "id": "219", + "children": [ + { + "name": "SortNode-137", + "id": "137", + "properties": { + "OrderBy": "{orderBy\u003d[device_id_3], orderings\u003d{device_id_3\u003dASC NULLS FIRST}}" + }, + "children": [ + { + "name": "ProjectNode-6", + "id": "6", + "properties": { + "OutputSymbols": [ + "device_id_3" + ], + "Expressions": [ + "\"device_id_3\"" + ] + }, + "children": [ + { + "name": "CteScanNode-2", + "id": "2" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } +} ++-----------------+ +``` + +### 4.2 Prepared Statement 与动态 SQL + +通过 `EXECUTE` 或 `EXECUTE IMMEDIATE` 分析预处理语句和动态 SQL 时,指定的输出格式会在语句展开后继续生效,不会退回默认的 `GRAPHVIZ` 或 `TEXT` 格式。 + +```SQL +PREPARE explain_json_stmt FROM SELECT * FROM table1 WHERE device_id = ?; +EXPLAIN (FORMAT JSON) EXECUTE explain_json_stmt USING '101'; + +EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE 'SELECT * FROM table1 WHERE device_id = ?' USING '101'; +``` + +其中: + +- `EXPLAIN (FORMAT JSON) EXECUTE ...` 返回 plan node JSON object。 + +- `EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE ...` 返回包含 `planStatistics`、`fragmentInstancesCount`、`fragmentInstances` 的 JSON object。 + +1. `EXPLAIN (FORMAT JSON) EXECUTE ...` 输出结构示例: + +```JSON ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-5", + "id": "5", + "properties": { + "OutputColumns": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ] + }, + "children": [ + { + "name": "DeviceTableScanNode-46", + "id": "46", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "region", + "plant_id", + "device_id", + "model_id", + "maintenance", + "temperature", + "humidity", + "status", + "arrival_time" + ], + "DeviceNumber": "3", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] +} ++-----------------+ +``` + +2. `EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE ...` 输出结构示例: + +```JSON ++---------------+ +|Explain Analyze| ++---------------+ +{ + "planStatistics": { + "analyzeCostMs": 2.904, + "fetchPartitionCostMs": 1.35, + "fetchSchemaCostMs": 5.259, + "logicalPlanCostMs": 1.123, + "logicalOptimizationCostMs": 6.157, + "distributionPlanCostMs": 0.571, + "dispatchCostMs": 5.909 + }, + "fragmentInstancesCount": 1, + "fragmentInstances": [ + { + "id": "20260722_065558_00118_1.2.0", + "ip": "127.0.0.1:10730", + "dataRegion": "1", + "state": "FINISHED", + "totalWallTimeMs": 10, + "initDataQuerySourceCostMs": 1.768, + "seqFileUnclosed": 0, + "seqFileClosed": 2, + "unseqFileUnclosed": 0, + "unseqFileClosed": 0, + "readyQueuedTimeMs": 0.059, + "blockQueuedTimeMs": 0.0, + "queryStatistics": { + "timeSeriesIndexFilteredRows": 0, + "chunkIndexFilteredRows": 0, + "pageIndexFilteredRows": 0 + }, + "operators": { + "planNodeId": "74", + "nodeType": "IdentitySinkNode", + "operatorType": "IdentitySinkOperator", + "cpuTimeMs": 9.193, + "outputRows": 10, + "hasNextCalledCount": 7, + "nextCalledCount": 6, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DownStreamPlanNodeId": "73" + }, + "children": [ + { + "planNodeId": "68", + "nodeType": "DeviceTableScanNode", + "operatorType": "TableScanOperator", + "cpuTimeMs": 9.055, + "outputRows": 10, + "hasNextCalledCount": 7, + "nextCalledCount": 6, + "estimatedMemorySize": 327680, + "specifiedInfo": { + "DeviceNumber": "3", + "CurrentDeviceIndex": "2" + } + } + ] + } + } + ] +} ++---------------+ +``` + +### 4.3 CLI 中的 JSON 展示 + +CLI 对普通查询结果默认按表格输出。对于 JSON 格式的查询分析结果,如果继续把 JSON 正文放入表格单元格,每一行都会带上 `|` 边框,不利于复制、保存和 JSON 解析。 + +当满足以下条件时,CLI 会切换为 raw JSON 展示: + +|条件|说明| +|---|---| +|结果集只有一列|避免影响普通多列查询展示| +|列名为 `distribution plan` 或 `Explain Analyze`|仅匹配 `EXPLAIN` / `EXPLAIN ANALYZE` 的结果列| +|首个结果值 trim 后以 `{` 或 `[` 开始|仅对 JSON object / array 内容启用 raw 输出| + +raw JSON 展示规则: + +- CLI 仍输出列名表头和外层分隔线,用户可以识别当前列为 `distribution plan` 或 `Explain Analyze`。 + +- JSON 正文逐行原样输出,不添加 `|` 表格边框。 + +- JSON 结束后输出分隔线和行数统计。 + +- 非 JSON 的 `EXPLAIN`、默认文本 `EXPLAIN ANALYZE`、普通查询结果仍走原有表格输出。 + +raw JSON 展示与普通表格输出的区别如下: + +|输出类型|适用结果|展示方式|复制与解析| +|---|---|---|---| +|普通表格输出|普通查询结果、默认 `EXPLAIN`、默认 `EXPLAIN ANALYZE`|每一行内容都放在表格边框内|适合人工查看,不适合直接作为 JSON 复制解析| +|raw JSON 输出|`EXPLAIN (FORMAT JSON)`、`EXPLAIN ANALYZE (FORMAT JSON)`|保留列名表头,JSON 正文不加表格边框|适合直接复制、保存或交给 JSON parser 解析| + +示例: + +```SQL +start-cli.sh -sql_dialect table -e "EXPLAIN (FORMAT JSON) SELECT time, device_id, temperature FROM database1.table1" +``` + +```SQL ++-----------------+ +|distribution plan| ++-----------------+ +{ + "name": "OutputNode-4", + "id": "4", + "properties": { + "OutputColumns": [ + "time", + "device_id", + "temperature" + ], + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ] + }, + "children": [ + { + "name": "CollectNode-42", + "id": "42", + "children": [ + { + "name": "ExchangeNode-49", + "id": "49", + "children": [ + { + "name": "DeviceTableScanNode-41", + "id": "41", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ], + "DeviceNumber": "4", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "1" + } + } + ] + }, + { + "name": "ExchangeNode-50", + "id": "50", + "children": [ + { + "name": "DeviceTableScanNode-40", + "id": "40", + "properties": { + "QualifiedTableName": "database1.table1", + "OutputSymbols": [ + "time", + "device_id", + "temperature" + ], + "DeviceNumber": "2", + "ScanOrder": "ASC", + "PushDownOffset": "0", + "PushDownLimit": "0", + "PushDownLimitToEachDevice": "false", + "RegionId": "2" + } + } + ] + } + ] + } + ] +} ++-----------------+ +``` + +### 4.4 非法格式组合 + +`EXPLAIN` 和 `EXPLAIN ANALYZE` 支持的格式不同。非法格式组合会返回明确错误。 + +|语句|默认格式|支持格式|不支持格式| +|---|---|---|---| +|`EXPLAIN`|`GRAPHVIZ`|`GRAPHVIZ`、`JSON`|`TEXT`、`XML`、其他未知格式| +|`EXPLAIN ANALYZE`|`TEXT`|`TEXT`、`JSON`|`GRAPHVIZ`、`XML`、其他未知格式| + +示例: + +```SQL +EXPLAIN (FORMAT TEXT) SELECT * FROM table1; +EXPLAIN ANALYZE (FORMAT GRAPHVIZ) SELECT * FROM table1; +EXPLAIN (FORMAT XML) SELECT * FROM table1; +``` + +对应报错: + +```SQL +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN format: TEXT. Supported formats: GRAPHVIZ, JSON +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN ANALYZE format: GRAPHVIZ. Supported formats: TEXT, JSON +Msg: org.apache.iotdb.jdbc.IoTDBSQLException: 701: Invalid EXPLAIN format: XML. Supported formats: GRAPHVIZ, JSON +``` + +## 5. 常见问题 + +### 5.1 查询超时时怎么办? + +`EXPLAIN ANALYZE` 会真实执行查询,因此也可能在查询超时时无法返回完整结果。为辅助排查,IoTDB 会按一定时间间隔将当前分析结果以文本形式写入专用日志;日志间隔根据查询超时时间计算,可保证超时前至少记录两次结果。 + +遇到超时时,按以下顺序处理: + +1. 查看 `logs/log_explain_analyze.log` 中对应查询的阶段性分析结果。 + +2. 如果日志中没有结果,检查升级时是否只替换了 lib 包,而未同步替换 `conf/logback-datanode.xml`。 + +3. 替换配置后无需重启。等待配置热加载完成,再执行 `EXPLAIN ANALYZE VERBOSE`。 + +### 5.2 WALL TIME 和 CPU TIME 有什么区别? + +CPU 时间指程序在执行过程中实际占用 CPU 进行计算的时间,表示程序实际消耗的处理器资源。 + +墙上时间指从程序开始执行到结束的真实物理时间,包括资源等待时间。 + +`WALL TIME < CPU TIME` 常见于并行执行场景。例如一个查询分片被两个线程并行执行,物理时间过去 10 秒,但两个线程各占用一个 CPU 核运行 10 秒,则 CPU TIME 约为 20 秒,WALL TIME 约为 10 秒。 + +`WALL TIME > CPU TIME` 常见于等待资源场景。例如查询分片因内存不足或等待上游数据进入 blocked queue,或者因查询线程资源不足进入 ready queue。等待期间不占用 CPU,但物理时间仍在流逝。 + +### 5.3 EXPLAIN ANALYZE 是否有额外开销? + +统计信息采集本身几乎没有显著的额外开销。`EXPLAIN ANALYZE` 算子会收集原查询已有的统计信息,并通过 `next` 遍历但不打印查询结果,因此与直接执行同一查询的耗时通常没有显著差别。 + +需要注意的是,被分析的查询仍会完整执行并正常消耗 CPU、内存和 IO 资源。评估生产环境影响时,应以原查询本身的资源消耗为准。 + +### 5.4 IO 耗时主要关注哪些指标? + +涉及 IO 耗时的指标主要包括 `loadBloomFilterActualIOSize`、`loadBloomFilterTime`、`loadTimeSeriesMetadataAlignedDiskSeqTime`、`loadTimeSeriesMetadataAlignedDiskUnseqTime`、`loadTimeSeriesMetadataActualIOSize`、`alignedTimeSeriesMetadataModificationTime`、`constructAlignedChunkReadersDiskTime`、`loadChunkActualIOSize`。 + +TimeSeriesMetadata 的加载分别统计顺序文件和乱序文件,但 Chunk 的读取暂时未分开统计顺序和乱序比例,可以通过 TimeSeriesMetadata 的顺乱序比例进行估算。 + +### 5.5 乱序数据对查询性能的影响如何观测? + +乱序数据主要产生两类影响: + +1. 查询时需要在内存中多做一次归并排序。 + +2. 乱序数据会产生数据块间的时间范围重叠,导致部分统计信息无法直接用于跳过不满足条件的数据块,或无法直接计算聚合值。 + +当前没有单独针对乱序数据影响的直接观测指标。通常可以在存在乱序数据时执行一次查询,待乱序数据合并完成后再次执行查询,通过前后耗时对比进行评估。