查询性能分析
查询性能分析
1. 概述
查询分析用于帮助用户理解查询的执行机制和性能瓶颈,从而进行查询优化和性能调优。IoTDB 表模型提供两类查询分析语句:
EXPLAIN:预览查询 SQL 的执行计划,展示 IoTDB 如何组织数据检索和处理。EXPLAIN ANALYZE:在EXPLAIN基础上真实执行查询,并展示查询执行过程中的时间、资源消耗和算子统计信息。
EXPLAIN 和 EXPLAIN ANALYZE 默认输出面向人工阅读,适合在 CLI 中直接查看。对于 Web Console、CI 回归测试、自动化性能诊断系统等需要稳定解析查询计划或执行统计的场景,可以使用 JSON 输出格式获取结构化结果。JSON 格式自 V2.0.11.1 起支持。
1.1 查询分析方式对比
与监控面板、Arthas 抽样等排查手段相比,EXPLAIN ANALYZE 无需部署额外组件,可以针对单条 SQL 进行分布式追踪,更适合定位具体查询的性能问题。
| 方法 | 安装难度 | 业务影响 | 功能范围 |
|---|---|---|---|
EXPLAIN ANALYZE 语句 | 低。无需安装额外组件,为 IoTDB 内置 SQL 语句 | 低。只会影响当前分析的单条查询,对线上其他负载无影响 | 支持分布式,可对单条 SQL 进行追踪 |
| 监控面板 | 中。需要安装 IoTDB 监控面板工具,并开启 IoTDB 监控服务 | 中。IoTDB 监控服务记录指标会带来额外耗时 | 支持分布式,仅支持对数据库整体查询负载和耗时进行分析 |
| Arthas 抽样 | 中。需要安装 Java Arthas 工具 | 高。CPU 抽样可能会影响线上业务响应速度 | 不支持分布式,仅支持对数据库整体查询负载和耗时进行分析 |
2. EXPLAIN
2.1 语法
EXPLAIN 命令用于查看 SQL 查询的分布式执行计划。执行计划以算子树的形式展示,描述 IoTDB 将如何执行查询。
EXPLAIN [(FORMAT { GRAPHVIZ | JSON })] <SELECT_STATEMENT>其中:
| 参数 | 说明 |
|---|---|
SELECT_STATEMENT | 需要分析的查询语句 |
FORMAT GRAPHVIZ | 以默认图形文本形式输出分布式计划,适合人工阅读 |
FORMAT JSON | 以 JSON 对象输出分布式计划,适合程序解析(自 V2.0.11.1 起支持) |
FORMAT 后的格式名大小写不敏感,例如 FORMAT json 与 FORMAT JSON 等价。
2.2 默认 GRAPHVIZ 格式
EXPLAIN 默认格式为 GRAPHVIZ,执行后将得到 distribution plan 结果列。
以示例数据中的 table1 为例,以下两条语句等价:
EXPLAIN SELECT * FROM table1;
EXPLAIN (FORMAT GRAPHVIZ) SELECT * FROM table1;执行如上语句后,输出如下:IoTDB 通过 DeviceTableScanNode 节点从不同数据分区读取数据,并通过 Collect 算子汇总后返回。
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 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,而是包装为:
{
"cteQueries": [
{
"name": "cte1",
"plan": {
"name": "OutputNode-<id>",
"id": "<id>"
}
}
],
"mainQuery": {
"name": "OutputNode-<id>",
"id": "<id>"
}
}其中 cteQueries[].plan 与 mainQuery 均为 plan node JSON object。该结构用于同时保留 CTE 子查询计划与主查询计划。
以示例数据中的 table1 为例:
EXPLAIN (FORMAT JSON) SELECT * FROM table1;+-----------------+
|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 不同,它会真实执行对应查询计划并统计执行信息,可用于追踪一条查询的具体性能分布,辅助资源观察、性能调优和异常分析。
EXPLAIN ANALYZE [VERBOSE] [(FORMAT { TEXT | JSON })] <SELECT_STATEMENT>其中:
| 参数 | 说明 |
|---|---|
SELECT_STATEMENT | 需要分析的查询语句 |
VERBOSE | 打印更详细的分析结果。不填写时会省略部分细粒度统计 |
FORMAT TEXT | 以默认文本形式输出分析结果,适合人工阅读 |
FORMAT JSON | 以 JSON 对象输出分析结果,适合程序解析(自 V2.0.11.1 起支持) |
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 以及是否触发过滤下推而变化。未触发对应逻辑时,相关字段可能不会出现在结果中。
- 文本输出示例
以示例数据中的 table1 为例,以下两条语句等价:
EXPLAIN ANALYZE SELECT * FROM table1;
EXPLAIN ANALYZE (FORMAT TEXT) SELECT * FROM table1;+-------------------------------------------------------------------------------------------------------------------------+
| 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 可以查看更细粒度的数据读取和过滤统计:
EXPLAIN ANALYZE VERBOSE SELECT * FROM table1;
EXPLAIN ANALYZE VERBOSE (FORMAT TEXT) SELECT * FROM table1;+-------------------------------------------------------------------------------------------------------------------------+
| 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.1 起支持。
3.3.1 默认 JSON 输出
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 数组 |
以示例数据中的 table1 为例:
EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM table1;+---------------+
|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 |
以示例数据中的 table1 为例:
EXPLAIN ANALYZE VERBOSE (FORMAT JSON) SELECT * FROM table1;+---------------+
|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 的包装结构。
以示例数据中的 table1 和 table2 为例:
EXPLAIN (FORMAT JSON)
WITH cte1 AS MATERIALIZED (SELECT * FROM table2)
SELECT * FROM table1
WHERE table1.device_id IN (SELECT device_id FROM cte1);+-----------------+
|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 格式。
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。
EXPLAIN (FORMAT JSON) EXECUTE ...输出结构示例:
+-----------------+
|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"
}
}
]
}
+-----------------+EXPLAIN ANALYZE (FORMAT JSON) EXECUTE IMMEDIATE ...输出结构示例:
+---------------+
|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 解析 |
示例:
start-cli.sh -sql_dialect table -e "EXPLAIN (FORMAT JSON) SELECT time, device_id, temperature FROM database1.table1"+-----------------+
|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、其他未知格式 |
示例:
EXPLAIN (FORMAT TEXT) SELECT * FROM table1;
EXPLAIN ANALYZE (FORMAT GRAPHVIZ) SELECT * FROM table1;
EXPLAIN (FORMAT XML) SELECT * FROM table1;对应报错:
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, JSON5. 常见问题
5.1 查询超时时怎么办?
EXPLAIN ANALYZE 会真实执行查询,因此也可能在查询超时时无法返回完整结果。为辅助排查,IoTDB 会按一定时间间隔将当前分析结果以文本形式写入专用日志;日志间隔根据查询超时时间计算,可保证超时前至少记录两次结果。
遇到超时时,按以下顺序处理:
查看
logs/log_explain_analyze.log中对应查询的阶段性分析结果。如果日志中没有结果,检查升级时是否只替换了 lib 包,而未同步替换
conf/logback-datanode.xml。替换配置后无需重启。等待配置热加载完成,再执行
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 乱序数据对查询性能的影响如何观测?
乱序数据主要产生两类影响:
查询时需要在内存中多做一次归并排序。
乱序数据会产生数据块间的时间范围重叠,导致部分统计信息无法直接用于跳过不满足条件的数据块,或无法直接计算聚合值。
当前没有单独针对乱序数据影响的直接观测指标。通常可以在存在乱序数据时执行一次查询,待乱序数据合并完成后再次执行查询,通过前后耗时对比进行评估。