EXPLAIN
EXPLAIN 用于提供语句的执行计划。
Syntax
EXPLAIN [ANALYZE] [VERBOSE] SELECT ...
ANALYZE 子句将执行语句并测量每个计划节点花费的时间以及输出的总行数等。
VERBOSE 子句可以进一步提供执行计划时详细的信息。
示 例
Explain 以下的查询:
EXPLAIN SELECT * FROM monitor where host='host1'\G
样例输出:
*************************** 1. row ***************************
plan_type: logical_plan
plan: MergeScan [is_placeholder=false]
*************************** 2. row ***************************
plan_type: physical_plan
plan: MergeScanExec: peers=[4612794875904(1074, 0), ]
plan_type 列指示了是 logical_plan 还是 physical_plan,plan 列详细说明了执行计划。
MergeScan 计划负责合并多个 region 的查询结果。物理计划 MergeScanExec 中的 peers 数组包含了将要扫描的 region 的 ID。
使用 ANALYZE 解释执行计划:
以下 ANALYZE 输出为示意,耗时指标已简化,部分扫描器展示字段也已省略。
例如,即使查询没有时间谓词,这些扫描也会包含无界的 time_range。
EXPLAIN ANALYZE SELECT * FROM monitor where host='host1'\G
样例输出:
*************************** 1. row ***************************
stage: 0
node: 0
plan: MergeScanExec: peers=[4612794875904(1074, 0), ] metrics=[output_rows: 0, greptime_exec_read_cost: 0, finish_time: 3301415, first_consume_time: 3299166, ready_time: 3104209, ]
*************************** 2. row ***************************
stage: 1
node: 0
plan: SeqScan: region=4612794875904(1074, 0), "partition_count":{"count":0,"mem_ranges":0,"files":0,"file_ranges":0}, "projection": ["host", "ts", "cpu", "memory"], "filters": ["host = Utf8(\"host1\")"], "file_summary":{"files":0,"rows":0,"row_groups":0,"size":0,"index_size":0} metrics=[output_rows: 0, ...]
*************************** 3. row ***************************
stage: NULL
node: NULL
plan: Total rows: 0
EXPLAIN ANALYZE 语句提供了每个执行阶段的指标。SeqScan 计划会扫描一个 region 的数据。
获取查询执行更详细的信息:
EXPLAIN ANALYZE VERBOSE SELECT * FROM monitor where host='host1';
样例输出:
*************************** 1. row ***************************
stage: 0
node: 0
plan: MergeScanExec: peers=[4612794875904(1074, 0), ] metrics=[output_rows: 0, greptime_exec_read_cost: 0, finish_time: 3479084, first_consume_time: 3476000, ready_time: 3209041, ]
*************************** 2. row ***************************
stage: 1
node: 0
plan: SeqScan: region=4612794875904(1074, 0), {"partition_count":{"count":0,"mem_ranges":0,"files":0,"file_ranges":0}, "projection": ["host", "ts", "cpu", "memory"], "filters": ["host = Utf8(\"host1\")"], "file_summary":{"files":0,"rows":0,"row_groups":0,"size":0,"index_size":0}, "flat_format": true} metrics=[output_rows: 0, ...]
*************************** 3. row ***************************
stage: NULL
node: NULL
plan: Total rows: 0
EXPLAIN ANALYZE VERBOSE 语句会展示计划执行阶段更详细的指标信息。
Scanner Metrics
扫描器节点,包括 SeqScan、SeriesScan 和 UnorderedScan,可以在
EXPLAIN ANALYZE 和 EXPLAIN ANALYZE VERBOSE 输出中打印扫描器相关指标。
这些指标可用于诊断读路径行为,例如分区、SST 剪枝、索引使用、缓存行为和扫描器耗时。
verbose 模式下的扫描器指标是诊断文本,不是稳定的公开 API。可选的零值字段通常会被省略。
输出结构
一行扫描器输出包含三层信息:
- 末尾
metrics=[...]之前的扫描器展示字段。 metrics_per_partition,只在 verbose 模式下打印,分别保留每个分区的PartitionMetrics快照。- 末尾
metrics=[...]中的 DataFusion 聚合指标。
在 metrics_per_partition 中,partition 指 DataFusion 执行计划的输出分区。
它不是 GreptimeDB 的 region,也不是通过 PARTITION ON COLUMNS(...) 创建的表分区。
一个扫描器指标分区可能覆盖一个或多个 GreptimeDB 扫描范围。
普通模式结构:
<Scanner>: region=<region>, <scanner display fields> metrics=[<aggregate metrics>]
verbose 模式结构:
<Scanner>: region=<region>, {
"partition_count": {...},
"projection": [...],
"filters": [...],
"dyn_filters": [...],
"time_range": "TimestampRange{[0ms,6000ms)}",
"file_summary": {...},
"max_part_range": {...},
"files": [...],
"flat_format": true,
"metrics_per_partition": [
{
"partition": <partition>,
"metrics": {
<per-partition metrics>,
"fetch_metrics": {...},
"metadata_cache_metrics": {...},
"top_file_metrics": {...}
}
}
]
} metrics=[<aggregate metrics>]
聚合指标
末尾的 metrics=[...] 部分是扫描器计划节点的 DataFusion 聚合输出。
扫描器分区会通过 ExecutionPlanMetricsSet 记录这些值,计划输出会跨分区聚合它们。
具体的聚合字段可能随版本和执行路径变化。输出中也可能包含其他运行时字段,
例如 mem_used、elapsed_poll 和 elapsed_await。
| 指标 | 含义 |
|---|---|
output_rows | 扫描器计划节点返回的行数。 |
elapsed_compute | DataFusion 上报的扫描器聚合计算耗时。 |
build_parts_cost | 构建 SST 文件范围的聚合耗时。 |
build_reader_cost | 构建 reader 或 merge reader 的聚合耗时。 |
convert_cost | 将 batch 转换为 Arrow record batch 的聚合耗时。 |
scan_cost | 扫描输入数据的聚合耗时。 |
yield_cost | 向下游输出 batch 后等待的聚合耗时。 |
使用合成值的示例:
SeqScan: region=0(1, 0), "partition_count":{"count":2, "mem_ranges":1, "files":1, "file_ranges":1}, "projection": ["host", "ts", "value"], "file_summary":{"files":1,"rows":1024,"row_groups":1,"size":65536,"index_size":4096}, "max_part_range":{"files":1,"rows":1024,"row_groups":1,"size":65536,"index_size":4096,"estimated_rows":1024,"time_range_start":"1000::Millisecond","time_range_end":"2000::Millisecond"} metrics=[output_rows: 128, elapsed_compute: 12ms, build_parts_cost: 1.2ms, build_reader_cost: 2.1ms, convert_cost: 300µs, scan_cost: 8.4ms, yield_cost: 900µs, ]
扫描器展示字段
扫描器展示字段描述扫描器将读取什么,以及附加了哪些谓词或投影。
EXPLAIN ANALYZE 只打印非 verbose 字段。EXPLAIN ANALYZE VERBOSE
会增加详细的扫描器输入和分区级指标。
| 字段 | 模式 | 含义 | 何时出现 |
|---|---|---|---|
| Scanner name | 全部 | 物理扫描器,例如 SeqScan、SeriesScan 或 UnorderedScan。 | 总是出现。 |
region | 全部 | 此计划节点扫描的 region ID。 | 总是出现。 |
partition_count.count | 全部 | 扫描器中的分区范围数量。 | 总是出现。 |
partition_count.mem_ranges | 全部 | 所有分区范围中的 memtable range 数量。 | 总是出现。 |
partition_count.files | 全部 | 范围展开前扫描输入中的 SST 文件数量。 | 总是出现。 |
partition_count.file_ranges | 全部 | 所有分区范围中的 SST 文件范围数量。 | 总是出现。 |
partition_count.other_ranges | 全部 | extension 或非 memtable、非 SST 范围数量。 | 非零时出现。 |
selector | 全部 | 附加到扫描的 series row selector。 | 存在 series row selector 时。 |
distribution | 全部 | 附加到扫描的分布信息。 | 存在 distribution 时。 |
mode | 全部 | Series scan 模式:two_phase 表示新的扫描路径,legacy 表示旧版扫描路径。 | SeriesScan 中总是出现。 |
projection | 全部 | 投影剪枝后的输出列名。 | 输出 schema 非空时。 |
filters | 全部 | 下推到扫描器的静态物理谓词表达式。这些谓词可能驱动 row group 剪枝、索引应用和精确过滤。 | 存在静态谓词时。 |
dyn_filters | 全部 | 计划创建后附加的动态过滤表达式。上游算子产生过滤值时,这些表达式可能变化。 | 存在动态过滤器时。 |
time_range | 全部 | 下推到扫描的时间范围,以 TimestampRange 字符串表示。 | 存在时间范围时。 |
file_summary | 全部 | 扫描输入中 SST 文件的元数据汇总。 | 扫描计划可用时总是出现,无 SST 时各项为零。 |
max_part_range | 全部 | 包含最多不同 SST 文件的分区范围的汇总。 | 至少一个范围包含 SST 文件时。 |
files | Verbose | 扫描输入中 SST 文件的元数据。 | 存在 SST 文件时。 |
flat_format | Verbose | 扫描输入是否预期使用 flat format。 | verbose 模式下总是出现。 |
extension_ranges | Verbose | 附加到扫描的企业版 extension ranges。 | 仅企业版构建且存在 extension ranges 时。 |
metrics_per_partition | Verbose | PartitionMetrics 收集的分区级扫描器指标。 | verbose 模式下分区有指标后。 |
file_summary 包含下表中的汇总字段。max_part_range 包含所选范围的相同汇总字段,
以及表中标出的额外字段。
| 字段 | 含义 |
|---|---|
files | 不同 SST 文件的数量,不包括 memtable 和企业版扩展数据源。 |
rows | SST 元数据中记录的行数之和。 |
row_groups | SST 元数据中记录的 row group 数量之和。 |
size | SST 文件大小之和,单位为字节。 |
index_size | 这些文件记录的索引大小之和,单位为字节。 |
time_range_start、time_range_end | 仅出现在 max_part_range:范围的闭区间边界,格式为 value::unit。 |
estimated_rows | 仅出现在 max_part_range:所选分区范围的估算行数。 |
这些汇总值描述输入文件的元数据,不是过滤后的返回行数或实际读取的字节数。
旧 SST 中未知的行数或 row group 数量可能记为零。一个文件即使拆分为多个 row group,
在同一范围内也只计数一次。max_part_range 按文件数量选择范围,不按字节数或行数选择,
也不暴露范围 ID。它的 rows 汇总完整文件的元数据行 数,而 estimated_rows 估算该范围内的行数。
普通的非 verbose EXPLAIN ANALYZE 输出,以及 POST /v1/sql/analyze/stream
首次包含扫描计划的 SSE 事件,都会提供这些扫描详情。较早的事件可能还没有扫描计划;
执行较快的查询可能直到 final 事件才提供计划。完整的 files 列表仍仅在 verbose 模式下
出现,包括最终的 verbose SSE 输出。
在 EXPLAIN ANALYZE FORMAT JSON 和 SSE 计划节点中,param 仍是包含扫描诊断信息的
字符串。这些字段不会引入 scan_details 包装层,也不会改变 JSON 节点结构。
投影列和过滤表达式字符串按 JSON 规则转义。扫描诊断文本仍可能随版本变化。
每个 files 项包含:
| 字段 | 含义 |
|---|---|
file_id | SST 文件的 region file ID。 |
time_range_start | 文件时间范围的闭区间起点,格式为 value::unit。 |
time_range_end | 文件时间范围的闭区间终点,格式为 value::unit。 |
rows | 文件元数据记录的行数。 |
size | SST 文件大小,单位为字节。 |
index_size | 文件记录的总索引大小,单位为字节。 |
使用合成值的 verbose 扫描器展示示例:
UnorderedScan: region=0(1, 0), {"partition_count":{"count":1, "mem_ranges":0, "files":1, "file_ranges":1}, "projection": ["host", "ts", "value"], "filters": ["host = Utf8(\"demo\")"], "dyn_filters": ["ts@1 < @runtime_filter"], "time_range": "TimestampRange{[0ms,6000ms)}", "file_summary":{"files":1,"rows":1024,"row_groups":1,"size":65536,"index_size":4096}, "max_part_range":{"files":1,"rows":1024,"row_groups":1,"size":65536,"index_size":4096,"estimated_rows":1024,"time_range_start":"1000::Millisecond","time_range_end":"2000::Millisecond"}, "files": [{"file_id":"0(1, 0)/00000000-0000-0000-0000-000000000001","time_range_start":"1000::Millisecond","time_range_end":"2000::Millisecond","rows":1024,"size":65536,"index_size":4096}], "flat_format": true, "metrics_per_partition": [...]}
分区级指标
metrics_per_partition 是只在 verbose 模式下出现的列表。每一项包含分区编号和该分区的指标快照。
即使指标名称重叠,这些值也不属于聚合 metrics=[...] 输出层。
使用合成值的示例:
{
"metrics_per_partition": [
{
"partition": 0,
"metrics": {
"prepare_scan_cost": "500µs",
"build_reader_cost": "2ms",
"scan_cost": "8ms",
"yield_cost": "1ms",
"total_cost": "12ms",
"num_rows": 128,
"num_batches": 4,
"num_mem_ranges": 1,
"num_file_ranges": 1,
"build_parts_cost": "1ms",
"sst_scan_cost": "6ms",
"rg_total": 3,
"rows_before_filter": 4096,
"num_sst_record_batches": 2,
"num_sst_batches": 2,
"num_sst_rows": 96,
"first_poll": "600µs",
"convert_cost": "300µs",
"rg_bloom_filtered": 1,
"rows_bloom_filtered": 1024,
"fetch_metrics": {
"total_fetch_elapsed": "2ms",
"page_cache_hit": 2,
"cache_miss": 1
},
"metadata_cache_metrics": {
"metadata_load_cost": "100µs",
"mem_cache_hit": 1
},
"build_ranges_peak_mem_size": 2048,
"num_peak_range_builders": 1,
"stream_eof": true
}
}
]
}
三个扫描器都使用相同的 PartitionMetrics 和 ScanMetricsSet 结构输出 verbose 分区级指标。
耗时和输出
| 指标 | 扫描器 | 含义 | 何时出现 |
|---|---|---|---|
prepare_scan_cost | 全部 | 从查询开始到创建分区指标的耗时。 | 总是出现。 |
build_reader_cost | 全部 | 在此分区中构建 reader 或 merge reader 的耗时。 | 总是出现。 |
scan_cost | 全部 | 在此分区中轮询扫描器输入的耗时。 | 总是出现。 |
yield_cost | 全部 | 向下游算子输出 batch 后的耗时。 | 总是出现。 |
convert_cost | 全部 | 在此分区中将 batch 转换为 Arrow record batch 的耗时。 | 记录转换耗时时。 |
total_cost | 全部 | 从查询开始到分区结束,或到指标对象被 drop 的耗时。 | 总是出现。 |
first_poll | 全部 | 从查询开始到分区 stream 首次被 poll 的耗时。 | 总是出现。 |
num_rows | 全部 | 此分区返回的行数。 | 总是出现。 |
num_batches | 全部 | 此分区返回的 batch 数量。 | 总是出现。 |
num_distributor_rows | SeriesScan | series distributor 扫描的行数。 | 非零时出现。 |
num_distributor_batches | SeriesScan | series distributor 扫描的 batch 数量。 | 非零时出现。 |
distributor_scan_cost | SeriesScan | series distributor 扫描输入的耗时。 | 非零时出现。 |
distributor_yield_cost | SeriesScan | series distributor 向分区 channel 发送 batch 的耗时。 | 非零时出现。 |
distributor_divider_cost | SeriesScan | 将 flat batch 拆分为 series batch 的耗时。 | 非零时出现。 |
Ranges 和 SST
| 指标 | 扫描器 | 含义 | 何时出现 |
|---|---|---|---|
num_mem_ranges | 全部 | 此分区扫描的 memtable ranges 数量。 | 总是出现。 |
num_file_ranges | 全部 | 此分区扫描的 SST 文件范围数量。 | 总是出现。 |
build_parts_cost | 全部 | 在此分区中构建 SST 文件范围的耗时。 | 总是出现。 |
sst_scan_cost | 全部 | 扫描 SST readers 的耗时。 | 总是出现。 |
rg_total | 全部 | 剪枝前考虑的 row groups 数量。 | 总是出现。 |
num_sst_record_batches | 全部 | 从 SST readers 读取的 Arrow record batch 数量。 | 总是出现。 |
num_sst_batches | 全部 | 从 SST readers 解码出的 batch 数量。 | 总是出现。 |
num_sst_rows | 全部 | 从 SST readers 解码出的行数。 | 总是出现。 |
过滤和剪枝
| 指标 | 扫描器 | 含义 | 何时出现 |
|---|---|---|---|
rg_fulltext_filtered | 全部 | 被全文索引过滤的 row groups 数量。 | 非零时出现。 |
rg_inverted_filtered | 全部 | 被倒排索引过滤的 row groups 数量。 | 非零时出现。 |
rg_minmax_filtered | 全部 | 被 min-max 剪枝过滤的 row groups 数量。 | 非零时出现。 |
rg_bloom_filtered | 全部 | 被 bloom filter 索引过滤的 row groups 数量。 | 非零时出现。 |
rows_before_filter | 全部 | 行级过滤前候选 row groups 中的行数。 | 总是出现。 |
rows_fulltext_filtered | 全部 | 被全文索引过滤的行数。 | 非零时出现。 |
rows_inverted_filtered | 全部 | 被倒排索引过滤的行数。 | 非零时出现。 |
rows_bloom_filtered | 全部 | 被 bloom filter 索引过滤的行数。 | 非零时出现。 |
rows_precise_filtered | 全部 | 被精确行级过滤器过滤的行数。 | 非零时出现。 |
pruner_cache_hit | 全部 | 构建文件范围时的 pruner builder 缓存命中次数。 | 非零时出现。 |
pruner_cache_miss | 全部 | 构建文件范围时的 pruner builder 缓存未命中次数。 | 非零时出现。 |
pruner_prune_cost | 全部 | 等待 pruner 构建文件范围的耗时。 | 非零时出现。 |
索引结果缓存
| 指标 | 扫描器 | 含义 | 何时出现 |
|---|---|---|---|
fulltext_index_cache_hit | 全部 | 全文索引结果缓存命中次数。 | 非零时出现。 |
fulltext_index_cache_miss | 全部 | 全文索引结果缓存未命中次数。 | 非零时出现。 |
inverted_index_cache_hit | 全部 | 倒排索引结果缓存命中次数。 | 非零时出现。 |
inverted_index_cache_miss | 全部 | 倒排索引结果缓存未命中次数。 | 非零时出现。 |
bloom_filter_cache_hit | 全部 | bloom filter 索引结果缓存命中次数。 | 非零时出现。 |
bloom_filter_cache_miss | 全部 | bloom filter 索引结果缓存未命中次数。 | 非零时出现。 |
minmax_cache_hit | 全部 | min-max 剪枝缓存命中次数。 | 非零时出现。 |
minmax_cache_miss | 全部 | min-max 剪枝缓存未命中次数。 | 非零时出现。 |
Memtables
| 指标 | 扫描器 | 含义 | 何时出现 |
|---|---|---|---|
mem_scan_cost | 全部 | 扫描 memtables 的耗时。 | 非零时出现。 |
mem_rows | 全部 |