查询分析的意义在于帮助用户理解查询的执行机制和性能瓶颈,从而实现查询优化和性能提升。这不仅关乎到查询的执行效率,也直接影响到应用的用户体验和资源的有效利用。为了进行有效的查询分析,IoTDB 提供了查询分析语句:Explain 和 Explain Analyze。
| 方法 | 安装难度 | 业务影响 | 功能范围 |
|---|---|---|---|
| Explain Analyze语句 | 低。无需安装额外组件,为IoTDB内置SQL语句 | 低。只会影响当前分析的单条查询,对线上其他负载无影响 | 支持分布式,可支持对单条SQL进行追踪 |
| 监控面板 | 中。需要安装IoTDB监控面板工具(企业版工具),并开启IoTDB监控服务 | 中。IoTDB监控服务记录指标会带来额外耗时 | 支持分布式,仅支持对数据库整体查询负载和耗时进行分析 |
| Arthas抽样 | 中。需要安装Java Arthas工具(部分内网无法直接安装Arthas,且安装后,有时需要重启应用) | 高。CPU 抽样可能会影响线上业务的响应速度 | 不支持分布式,仅支持对数据库整体查询负载和耗时进行分析 |
Explain命令允许用户查看SQL查询的执行计划。执行计划以算子的形式展示,描述了IoTDB会如何执行查询。其语法如下,其中SELECT_STATEMENT是查询相关的SQL语句:
EXPLAIN <SELECT_STATEMENT>
-- 创建数据库 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 算子汇总数据后返回。
+-----------------------------------------------------------------------------------------------+ | 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 │| | └─────────────────────────────────────────────┘| +-----------------------------------------------------------------------------------------------+
EXPLAIN ANALYZE [VERBOSE] <SELECT_STATEMENT>
其中 SELECT_STATEMENT 对应需要分析的查询语句;VERBOSE为打印详细分析结果,不填写VERBOSE时EXPLAIN ANALYZE将会省略部分信息。
Explain Analyze 是 IOTDB 查询引擎自带的性能分析 SQL,与 Explain 不同,它会执行对应的查询计划并统计执行信息,可以用于追踪一条查询的具体性能分布,用于对资源进行观察,进行性能调优与异常分析。
在 EXPLAIN ANALYZE 的结果集中,会包含如下信息:
其中:
查询超时场景使用 Explain Analyze 语句:
Explain Analyze 本身是一种特殊的查询,所以当执行超时的时候,Explain Analyze 语句也无法正常返回结果。为了在查询超时的情况下也可以通过分析结果排查超时原因,Explain Analyze 提供了定时日志机制(无需用户配置),每经过一定的时间间隔会将 Explain Analyze 的当前结果以文本的形式输出到专门的日志中。当查询超时时,用户可以前往logs/log_explain_analyze.log中查看对应的日志进行排查。 日志的时间间隔基于查询的超时时间进行计算,可以保证在超时的情况下至少会有两次的结果记录。
下面是Explain Analyze的一个例子:
-- 创建数据库 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;
得到输出如下:
+-----------------------------------------------------------------------------------------------+ | 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 | +-----------------------------------------------------------------------------------------------+
CPU 时间也称为处理器时间或处理器使用时间,指的是程序在执行过程中实际占用 CPU 进行计算的时间,显示的是程序实际消耗的处理器资源。
墙上时间也称为实际时间或物理时间,指的是从程序开始执行到程序结束的总时间,包括了所有等待时间。。
几乎没有,因为 explain analyze operator 是单独的线程执行,收集原查询的统计信息,且这些统计信息,即使不explain analyze,原来的查询也会生成,只是没有人去取。并且 explain analyze 是纯 next 遍历结果集,不会打印,所以与原来查询真实执行时的耗时不会有显著差别。
涉及 IO 耗时的指标主要有:loadBloomFilterActualIOSize, loadBloomFilterTime, loadTimeSeriesMetadataAlignedDisk[Seq/Unseq]Time, loadTimeSeriesMetadataActualIOSize, alignedTimeSeriesMetadataModificationTime, constructAlignedChunkReadersDiskTime, loadChunkActualIOSize,各指标的具体含义见上文。 TimeSeriesMetadata 的加载分别统计了顺序和乱序文件,但 Chunk 的读取暂时未分开统计,但顺乱序比例可以通过TimeseriesMetadata 顺乱序的比例计算出来。
乱序数据产生的影响主要有两个:
单独对于乱序数据的性能影响,目前并没有有效的观测手段,除非就是在有乱序数据的时候,执行一遍查询耗时,然后等乱序合完了,再执行一遍,才能对比出来。
因为即使乱序这部分数据进了顺序,也是需要 IO、加压缩、decode,这个耗时少不了,不会因为乱序数据被合并进乱序了,就减少了。
升级时,只替换了 lib 包,没有替换 conf/logback-datanode.xml,需要替换一下 conf/logback-datanode.xml,然后不需要重启(该文件内容可以被热加载),大约等待 1 分钟后,重新执行 explain analyze verbose。