TDengine 查询引擎设计与最佳实践

TDengine 查询引擎设计与最佳实践

一、设计理念

TDengine 查询引擎专为时序数据场景优化,核心目标是:充分利用时序数据特征,通过智能优化和分布式计算,实现毫秒级查询响应

核心设计原则

  • 标签先行:先过滤标签,再扫描数据,大幅减少数据扫描量
  • 预计算优化:利用 SMA 统计信息,避免读取原始数据
  • 分布式并行:多 vnode 并行计算,充分利用集群资源
  • 智能缓存:多级缓存机制,加速热点数据访问

二、查询架构总览

复制代码
┌────────────────────────────────────────────────────┐
│                应用程序 (Application)           	 │
└────────────────┬───────────────────────────────────┘
                 ↓
┌────────────────────────────────────────────────────┐
│              taosc (客户端驱动)             	     │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│  │ SQL解析  │→│ 查询优化 │→│ 任务调度 │	             │
│  │  (AST)   │  │  (优化器) │  │ (调度器) │		     │
│  └──────────┘  └──────────┘  └──────────┘          │
└────────┬───────────────────────────┬───────────────┘
         ↓                           ↓
┌────────────────┐          ┌────────────────┐
│  mnode (元数据) │          │  vnode (数据) 	 │
│  - 表元数据   	 │          │  - 数据扫描   	 │
│  - vgroup信息 	 │          │  - 标签过滤 	 │
└────────────────┘          │  - 聚合计算	 │
                            └────────────────┘
                                    ↓
                            ┌────────────────┐
                            │  qnode (计算) 	 │
                            │  - 多表聚合   	 │
                            │  - 复杂计算   	 │
                            └────────────────┘

三、查询处理流程

3.1 完整查询流程

复制代码
┌─────────────────────────────────────────────────────┐
│ 第1步:SQL 解析与元数据获取            	      	      │
├─────────────────────────────────────────────────────┤
│ taosc 解析 SQL → 生成 AST                     	      │
│         ↓                               	   	  	  │
│ Catalog 向 mnode/vnode 请求元数据    	              │
│         ↓                                           │
│ 权限检查、语法校验、合法性校验                           │
└─────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────┐
│ 第2步:生成逻辑计划并优化                               │
├─────────────────────────────────────────────────────┤
│ 生成逻辑查询计划                                       │
│         ↓                                           │
│ 应用优化策略(谓词下推、投影下推等)                      │
│         ↓                                           │
│ 根据 vgroup 和 qnode 信息生成物理计划                   │
└─────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────┐
│ 第3步:任务调度                                       │
├─────────────────────────────────────────────────────┤
│ 调度器将子任务分发到 vnode/qnode                       │
│ 考虑数据亲缘性和负载均衡                                │
└─────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────┐
│ 第4-5步:执行查询                           	      │
├─────────────────────────────────────────────────────┤
│ vnode/qnode 接收任务 → 执行队列           	          │
│         ↓                                           │
│ 建立查询执行环境 → 执行查询 → 通知结果就绪                │
└─────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────┐
│ 第6-8步:结果返回                            	      │
├─────────────────────────────────────────────────────┤
│ taosc 按执行计划依次完成任务调度               	      │
│         ↓                                     	  │
│ 向查询节点发送数据获取请求        	                  │
│         ↓                                           │
│ 返回查询结果给应用程序   	    	                      │
└─────────────────────────────────────────────────────┘

3.2 超级表聚合查询流程(核心优势)

sql 复制代码
-- 典型的超级表聚合查询
SELECT location, AVG(temperature), MAX(humidity)
FROM sensors
WHERE ts >= '2024-01-01' AND ts < '2024-01-02'
  AND location IN ('北京', '上海')
GROUP BY location;

执行流程

复制代码
第1步:taosc → mnode 获取 sensors 超级表元数据
       返回:vgroup 分布信息、表结构信息

第2步:标签过滤(在元数据中完成,极快)
       location IN ('北京', '上海')
       ↓ 内存中过滤,确定需要扫描的子表集合
       找到:sensor_bj_001, sensor_bj_002, ..., sensor_sh_001, ...

第3步:时间过滤 + 数据分区定位
       ts >= '2024-01-01' AND ts < '2024-01-02'
       ↓ 根据时间范围计算文件组编号
       直接定位到对应的数据文件(无需扫描其他时间段)

第4步:并行计算(多 vnode 同时执行)
       vnode1: 扫描北京区域传感器数据 → 局部聚合
       vnode2: 扫描上海区域传感器数据 → 局部聚合
       vnode3: 扫描其他区域传感器数据 → 局部聚合

第5步:全局聚合(qnode 执行)
       qnode 收集各 vnode 的局部结果 → 最终聚合
       返回:location='北京', avg_temp=25.5, max_hum=60
              location='上海', avg_temp=26.3, max_hum=65

性能优势:
✓ 标签过滤在内存完成,毫秒级
✓ 时间分区直接定位文件,无需全表扫描
✓ 多 vnode 并行计算,速度线性提升
✓ 数据扫描量减少 90%+

四、核心查询优化技术

4.1 标签与数据分离存储

设计理念:标签数据与时序数据完全分离

复制代码
元数据层(META - B+Tree 存储):
├─ 超级表 sensors
│  ├─ schema: (ts TIMESTAMP, temperature FLOAT, humidity INT)
│  └─ tags: (location BINARY(64), device_id INT)
│
├─ 子表 sensor_001
│  └─ tags: {location: '北京', device_id: 1}
│
├─ 子表 sensor_002
│  └─ tags: {location: '上海', device_id: 2}
└─ ...

时序数据层(TSDB - LSM 存储):
├─ sensor_001 数据块
│  ├─ [时间列] [温度列] [湿度列]
│  └─ 压缩存储
│
├─ sensor_002 数据块
└─ ...

查询优势

sql 复制代码
-- 1. 标签过滤查询(极快)
SELECT COUNT(*) FROM sensors WHERE location = '北京';

执行:
1. 在元数据层扫描标签 → 找到 100 张北京的表
2. 只扫描这 100 张表的数据
   vs 传统方式:扫描全部 10000 张表
   
速度提升:100x+

-- 2. 标签更新(无需重写数据)
ALTER TABLE sensor_001 SET TAG location = '天津';

执行:
只更新元数据层的标签值,不涉及时序数据
速度:毫秒级

4.2 预计算(SMA)优化

BRIN 索引 + SMA 统计信息

c 复制代码
// 数据块统计信息(head 文件中)
struct DataBlockMeta {
    // 时间范围
    int64_t  minTimestamp;     // 2024-01-01 00:00:00
    int64_t  maxTimestamp;     // 2024-01-01 01:00:00
    
    // 预计算统计
    double   minValue[3];      // [20.5, 18.2, 50]  (温度、湿度、压力)
    double   maxValue[3];      // [30.8, 28.6, 70]
    double   sum[3];           // [25500, 23000, 60000]
    int32_t  count;            // 1000 条记录
    
    // 数据位置
    uint64_t dataOffset;       // 数据在 data 文件中的偏移
    uint32_t dataLength;       // 数据块大小
};

查询加速示例

sql 复制代码
-- 查询最大值(无需读取原始数据)
SELECT MAX(temperature), MIN(humidity), AVG(pressure)
FROM sensor_001
WHERE ts >= '2024-01-01' AND ts < '2024-01-02';

执行过程:
1. 根据时间范围定位数据文件组
2. 读取 head 文件中的 BRIN 索引(几 KB)
3. 扫描匹配时间范围的数据块元数据
4. 直接使用预计算值:
   - MAX(temperature) = max(所有块的maxValue[0])
   - MIN(humidity) = min(所有块的minValue[1])
   - AVG(pressure) = sum(所有块的sum[2]) / sum(所有块的count)
5. 返回结果

I/O 节省:99%+(只读索引,不读数据文件)
查询速度:毫秒级 vs 秒级

适用查询类型

  • ✅ COUNT、SUM、AVG、MAX、MIN
  • ✅ 大时间范围的聚合查询
  • ✅ 多表聚合统计

4.3 多级缓存机制

复制代码
┌───────────────────────────────────────────┐
│         缓存层次结构                        │
├───────────────────────────────────────────┤
│ L1: 元数据缓存(taosc)        	            │
│     - 表 schema                          	│
│     - vgroup 路由信息                    	│
│     - leader vnode 位置                 	│
│     策略:LRU,固定大小             	  	    │
│     命中率:90%+                          	│
├───────────────────────────────────────────┤
│ L2: 时序数据缓存(vnode 内存)       	    │
│     - 最新写入的数据                     	│
│     - SkipList 组织             	        │
│     策略:写驱动缓存              	        │
│     命中率(最新数据):99%+   	            │
├───────────────────────────────────────────┤
│ L3: last/last_row 缓存(vnode)       	    │
│     - 每张表的最后一条记录                	│
│     - 每列的最后一个非 NULL 值              	│
│     策略:LRU,延迟加载                     	│
│     命中率:95%+                      	    │
└───────────────────────────────────────────┘

缓存优化查询示例

sql 复制代码
-- 1. 最新数据查询(L2 缓存)
SELECT * FROM sensor_001 
WHERE ts >= NOW - 1h;

执行:直接从 vnode 内存缓存返回
响应时间:< 1ms

-- 2. Last Row 查询(L3 缓存)
SELECT LAST_ROW(*) FROM sensor_001;

执行:从 last_row 缓存返回
响应时间:< 1ms

-- 3. Last 查询(L3 缓存)
SELECT LAST(temperature), LAST(humidity) 
FROM sensors 
GROUP BY tbname;

执行:
1. 标签过滤找到所有子表
2. 从 last 缓存读取每张表的最后非 NULL 值
3. 组装返回

响应时间:毫秒级(vs 秒级)

4.4 查询策略选择

TDengine 提供 4 种查询策略(queryPolicy 配置):

sql 复制代码
-- 配置文件 taos.cfg
queryPolicy 1  -- 默认:仅使用 vnode
queryPolicy 2  -- 混合模式:vnode + qnode
queryPolicy 3  -- 存算分离:扫描用 vnode,计算用 qnode
queryPolicy 4  -- 客户端聚合:taosc 聚合

策略对比

策略 适用场景 优势 劣势
1 (vnode only) 简单查询、小数据量 延迟低、简单 vnode 负载高
2 (混合模式) 一般场景 平衡性能和资源 配置复杂
3 (存算分离) 复杂聚合、大数据量 充分利用 qnode 计算能力 网络传输开销
4 (客户端聚合) 小规模集群 减轻服务端压力 客户端性能要求高

五、SQL 扩展特性

5.1 PARTITION BY(分组扩展)

sql 复制代码
-- 按设备分组,计算每个设备的统计信息
SELECT location, device_id, 
       AVG(temperature) as avg_temp,
       STDDEV(temperature) as std_temp
FROM sensors
WHERE ts >= '2024-01-01'
PARTITION BY location, device_id;

执行优势:
- 数据按分组键切分
- 每组内独立计算,可并行
- 支持窗口函数和复杂运算

5.2 SLIMIT 与 SOFFSET(分组限制)

sql 复制代码
-- 限制返回的分组数量(不是记录数)
SELECT AVG(temperature)
FROM sensors
PARTITION BY device_id
SLIMIT 10 OFFSET 20;

说明:
- SLIMIT: 返回 10 个分组
- SOFFSET: 跳过前 20 个分组
- LIMIT: 限制每组内的记录数

5.3 窗口查询

sql 复制代码
-- 1. 时间窗口
SELECT _wstart, _wend, AVG(temperature)
FROM sensor_001
WHERE ts >= '2024-01-01'
INTERVAL(10m) SLIDING(5m);

-- 2. 会话窗口
SELECT _wstart, _wend, COUNT(*)
FROM sensor_001
SESSION(ts, 1h);

-- 3. 状态窗口
SELECT _wstart, _wend, COUNT(*)
FROM sensor_001
STATE_WINDOW(status);

-- 4. 事件窗口
SELECT _wstart, _wend, SUM(value)
FROM sensor_001
EVENT_WINDOW START WITH status=1 END WITH status=0;

5.4 时序特有的 JOIN

sql 复制代码
-- ASOF JOIN(时间匹配)
SELECT a.ts, a.temperature, b.humidity
FROM sensor_temp a
ASOF JOIN sensor_hum b
ON a.ts >= b.ts;

-- WINDOW JOIN(窗口关联)
SELECT a.location, AVG(a.temperature), AVG(b.humidity)
FROM sensor_temp a
WINDOW JOIN sensor_hum b
ON a.device_id = b.device_id
INTERVAL(1h);

六、查询最佳实践

6.1 ✅ 推荐的查询方法

1. 充分利用超级表 + 标签过滤
sql 复制代码
-- ✅ 好:先过滤标签,再扫描数据
SELECT AVG(temperature)
FROM sensors
WHERE location = '北京'      -- 标签过滤
  AND ts >= '2024-01-01'     -- 时间过滤
GROUP BY device_id;

性能:毫秒级
原因:
1. 标签过滤在元数据层完成,内存操作
2. 只扫描北京地区的表,数据量减少 90%+
3. 时间过滤直接定位文件组
2. 使用预计算函数
sql 复制代码
-- ✅ 好:使用 MAX/MIN/AVG 等预计算
SELECT MAX(temperature), MIN(temperature), AVG(humidity)
FROM sensors
WHERE ts >= '2024-01-01' AND ts < '2024-02-01';

性能:毫秒级
原因:直接使用 BRIN 索引中的统计信息,不读原始数据
3. 查询最新数据
sql 复制代码
-- ✅ 好:查询最新数据,命中缓存
SELECT * FROM sensor_001
WHERE ts >= NOW - 1h;

-- ✅ 好:使用 LAST_ROW
SELECT LAST_ROW(*) FROM sensor_001;

-- ✅ 好:使用 LAST
SELECT LAST(temperature) FROM sensor_001;

性能:< 1ms
原因:数据在内存缓存或 last/last_row 缓存中
4. 合理使用时间范围
sql 复制代码
-- ✅ 好:精确的时间范围
SELECT * FROM sensor_001
WHERE ts >= '2024-01-01 00:00:00'
  AND ts < '2024-01-01 01:00:00';

性能:快
原因:直接定位 1 小时的数据文件,扫描量最小
5. 利用窗口聚合
sql 复制代码
-- ✅ 好:使用时间窗口
SELECT _wstart, AVG(temperature)
FROM sensor_001
WHERE ts >= '2024-01-01'
INTERVAL(1h);

性能:快
原因:一次扫描完成分组聚合,高效
6. 批量查询
sql 复制代码
-- ✅ 好:一次查询多个指标
SELECT 
    AVG(temperature) as avg_temp,
    MAX(temperature) as max_temp,
    STDDEV(temperature) as std_temp,
    AVG(humidity) as avg_hum
FROM sensor_001
WHERE ts >= '2024-01-01';

性能:快
原因:一次数据扫描完成多个计算

6.2 ❌ 要避免的查询方法

1. 避免全表扫描
sql 复制代码
-- ❌ 差:无时间条件的全表扫描
SELECT * FROM sensor_001;

-- ❌ 差:无标签过滤的超级表查询
SELECT * FROM sensors;

问题:扫描全部数据,极慢
优化:
-- ✅ 好:加上时间条件
SELECT * FROM sensor_001
WHERE ts >= NOW - 7d;
2. 避免在数据列上过滤
sql 复制代码
-- ❌ 差:用数据列而非标签过滤
SELECT * FROM sensors
WHERE temperature > 25;    -- 需扫描所有数据

-- ✅ 好:用标签过滤
SELECT * FROM sensors
WHERE location = '北京'    -- 标签过滤,内存完成
  AND temperature > 25;    -- 数据过滤,扫描量已减少
3. 避免复杂子查询
sql 复制代码
-- ❌ 差:嵌套子查询
SELECT * FROM sensors
WHERE device_id IN (
    SELECT device_id FROM sensors
    WHERE temperature > 30
);

问题:多次扫描,性能差

-- ✅ 好:用 JOIN 或合并条件
SELECT DISTINCT device_id, AVG(temperature)
FROM sensors
WHERE temperature > 30
GROUP BY device_id;
4. 避免不必要的 DISTINCT
sql 复制代码
-- ❌ 差:对大数据集使用 DISTINCT
SELECT DISTINCT * FROM sensors
WHERE ts >= '2024-01-01';

问题:需要排序去重,内存占用大

-- ✅ 好:明确需要去重的列
SELECT DISTINCT device_id FROM sensors;
5. 避免 SELECT *
sql 复制代码
-- ❌ 差:查询所有列
SELECT * FROM sensors
WHERE ts >= '2024-01-01'
LIMIT 10;

-- ✅ 好:只查询需要的列
SELECT ts, temperature, humidity FROM sensors
WHERE ts >= '2024-01-01'
LIMIT 10;

原因:列式存储,只读取需要的列可节省 I/O
6. 避免跨大时间范围的计算
sql 复制代码
-- ❌ 差:跨年的 DIFF 计算
SELECT DIFF(value) FROM sensor_001
WHERE ts >= '2023-01-01' AND ts < '2024-12-31';

问题:DIFF 需要前后数据关联,跨度大性能差

-- ✅ 好:限制时间范围
SELECT DIFF(value) FROM sensor_001
WHERE ts >= NOW - 7d;
7. 避免在 WHERE 中使用函数
sql 复制代码
-- ❌ 差:WHERE 中使用函数
SELECT * FROM sensors
WHERE TO_TIMESTAMP(ts) >= '2024-01-01';

问题:无法利用索引,全表扫描

-- ✅ 好:直接比较
SELECT * FROM sensors
WHERE ts >= '2024-01-01 00:00:00';

七、性能优化建议

7.1 表设计优化

sql 复制代码
-- ✅ 好:合理设计标签
CREATE STABLE sensors (
    ts TIMESTAMP,
    temperature FLOAT,
    humidity INT
) TAGS (
    location BINARY(64),      -- 常用于过滤的字段
    building BINARY(64),      -- 常用于分组的字段
    floor INT,                -- 常用于统计的字段
    device_type BINARY(32)    -- 常用于分类的字段
);

设计原则:
1. 常用过滤条件作为标签
2. 标签数量适中(5-10 个)
3. 标签值不宜过多变化

7.2 索引优化

sql 复制代码
-- ✅ 好:创建标签索引
CREATE SMA INDEX temp_idx ON sensors FUNCTION(MAX(temperature), MIN(temperature))
INTERVAL(1h);

-- ✅ 好:创建时间分区
CREATE DATABASE sensor_db
    DURATION 10d           -- 每个文件组 10 天数据
    KEEP 365d;            -- 保留 365 天

7.3 查询优化配置

sql 复制代码
-- 1. 启用 last/last_row 缓存
CREATE DATABASE sensor_db
    CACHEMODEL 'both';    -- 启用 last 和 last_row 缓存

-- 2. 配置查询策略
-- 在 taos.cfg 中
queryPolicy 3            -- 存算分离,适合复杂查询

-- 3. 增加 vnode 内存
-- 在 taos.cfg 中
buffer 256               -- 每个 vnode 的写缓存(MB)

八、查询性能对比

案例:1000 万条记录的聚合查询

sql 复制代码
-- 查询:计算 24 小时平均温度
SELECT AVG(temperature) FROM sensor_001
WHERE ts >= '2024-01-01' AND ts < '2024-01-02';
优化方法 查询时间 优化效果
无优化(全表扫描) 5000ms 基准
+ 时间分区 500ms 10x
+ 列式存储 200ms 25x
+ BRIN 索引 + SMA 10ms 500x
+ 内存缓存(最新数据) < 1ms 5000x

案例:超级表多表聚合

sql 复制代码
-- 查询:10000 张表的温度统计
SELECT location, AVG(temperature)
FROM sensors
WHERE ts >= '2024-01-01'
GROUP BY location;
优化方法 查询时间 数据扫描量
传统数据库 60000ms 100%
TDengine(标签过滤) 2000ms 10%
TDengine(标签 + 并行) 200ms 10%
TDengine(全优化) 50ms < 1%

提升:1200x

九、总结

TDengine 查询引擎核心优势

  1. 标签与数据分离:内存标签过滤,减少 90%+ 数据扫描
  2. 时间分区:O(1) 时间定位数据文件,无需索引
  3. 预计算优化:BRIN + SMA,聚合查询不读原始数据
  4. 多级缓存:元数据、时序数据、最新数据多级缓存
  5. 分布式并行:多 vnode 并行计算,线性性能提升
  6. 列式存储:按列压缩和读取,I/O 节省 90%+

最佳实践核心要点

推荐做法

  • ✅ 使用超级表 + 标签过滤
  • ✅ 指定明确的时间范围
  • ✅ 查询最新数据(命中缓存)
  • ✅ 使用预计算函数(MAX/MIN/AVG)
  • ✅ 合理使用窗口查询
  • ✅ 只查询需要的列

避免做法

  • ❌ 全表扫描
  • ❌ 在数据列上过滤
  • ❌ 复杂嵌套子查询
  • ❌ 不必要的 DISTINCT
  • ❌ SELECT *
  • ❌ WHERE 中使用函数

性能提升数据

  • 标签过滤查询:100x ~ 1000x 提升
  • 预计算查询:100x ~ 500x 提升
  • 最新数据查询:1000x ~ 5000x 提升
  • 超级表聚合:100x ~ 1200x 提升

TDengine 查询引擎通过深度优化时序数据场景,实现了传统数据库难以企及的查询性能,特别是在多表聚合、时间范围查询和统计分析场景下,性能优势极其显著。


关于 TDengine

TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。

相关推荐
物联网软硬件开发-轨物科技1 小时前
【轨物方案】新能源的下半场:构筑光伏场站全生命周期智慧运维新范式
大数据·人工智能·物联网
一码归一码@1 小时前
Mysql进阶之事务原理
数据库·mysql
老邓计算机毕设8 小时前
SSM学生选课系统xvbna(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·学生选课系统·ssm 框架·高校教学管理
枷锁—sha9 小时前
【PortSwigger Academy】SQL 注入绕过登录 (Login Bypass)
数据库·sql·学习·安全·网络安全
汇智信科9 小时前
智慧矿山和工业大数据解决方案“智能设备管理系统”
大数据·人工智能·工业大数据·智能矿山·汇智信科·智能设备管理系统
阿里云大数据AI技术10 小时前
Hologres Dynamic Table 在淘天价格力的业务实践
大数据·人工智能·阿里云·hologres·增量刷新
逍遥德11 小时前
PostgreSQL 中唯一约束(UNIQUE CONSTRAINT) 和唯一索引(UNIQUE INDEX) 的核心区别
数据库·sql·postgresql·dba
工业甲酰苯胺11 小时前
字符串分割并展开成表格的SQL实现方法
数据库·sql
北京耐用通信11 小时前
耐达讯自动化Profibus总线光纤中继器:光伏逆变器通讯的“稳定纽带”
人工智能·物联网·网络协议·自动化·信息与通信
科技块儿11 小时前
IP定位技术:游戏反外挂体系中的精准识别引擎
数据库·tcp/ip·游戏