分类:4.查询引擎 | 篇章:10 排序填充投影

适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-06-18
排序(ORDER BY)、填充(FILL)和投影(Project)是查询后处理阶段的核心算子。本文聚焦于这三类算子的实现细节、与时序数据排序天然有序的关系、以及如何避免不必要的全局排序开销。
核心概念速查表
| 概念 | 说明 |
|---|---|
| ORDER BY ts | 按时间排序(天然有序,零开销) |
| ORDER BY 其他列 | 需要实际排序 |
| MergeSort | 多路有序输入合并 |
| TopN | ORDER BY + LIMIT N 的优化 |
| FILL | 时间窗口缺失值填充 |
| Project | 投影:选择/计算/重命名列 |
| Aliasing | 列别名(AS) |
详细解析
1. ORDER BY 的执行策略
TDengine ORDER BY 的三种执行情况:
① ORDER BY ts ASC(默认)
- 存储天然有序 → 零开销
- Scan 顺序读 → 直接输出
② ORDER BY ts DESC
- Scan 反向读取数据块
- 同样零开销
③ ORDER BY 其他列
- 必须实际排序
- 单节点:内存排序(小数据)/ 外排(大数据)
- 多节点:本地排序 + MergeExchange
2. MergeSort(多路归并)
跨 VGroup 的全局排序:
SQL: SELECT * FROM meters WHERE ts > now-1h ORDER BY ts;
执行:
┌─ Client/QNode ────────────────────┐
│ MergeSort (3 路归并) │
│ ↑ │
│ Exchange │
└────────┬───────────────────────────┘
│
┌──────┼──────┐
▼ ▼ ▼
┌───┐ ┌───┐ ┌───┐
│VN1│ │VN2│ │VN3│
│ │ │ │ │ │
│Scan│ │Scan│ │Scan│ (各自按 ts 有序)
└───┘ └───┘ └───┘
归并算法:
维护 3 个指针,指向每个 VNode 的当前位置
每次输出 ts 最小的那个,对应指针前进
类似数据文件多路合并(详见 04-file-format)
3. ORDER BY + LIMIT(TopN 优化)
TopN 优化:
SELECT * FROM meters ORDER BY ts DESC LIMIT 10;
朴素实现:
全表排序 → 取前 10
复杂度 O(N log N)
TopN 优化:
维护大小为 10 的堆
扫描中只保留 top 10
复杂度 O(N log 10)
跨节点的 TopN:
① 每个 VNode 各取 top 10
② 共 K×10 行汇总到 QNode
③ QNode 再做 top 10
网络传输:仅 K×10 行(不是全量)
ORDER BY 时间列 + LIMIT 特别优化:
ORDER BY ts DESC LIMIT 10
→ 反向 Scan,读到 10 行立即停止
→ I/O 极小
4. 外排序(大数据排序)
当排序数据超出内存:
① 数据按内存大小分块读入
② 每块内存中排序后写入临时文件
③ 多路归并所有临时文件
特点:
- 磁盘 I/O 大(写临时文件 + 读取归并)
- 性能远低于内存排序
- 应尽量避免(用 LIMIT 限制)
避免外排的方法:
- 加 WHERE 限制数据量
- 用 LIMIT + TopN 算法
- 增大查询内存配额
5. FILL 算子的执行
FILL 实现流程:
INTERVAL(1h) FILL(LINEAR)
窗口算子输出:
12:00 → 25.0
13:00 → (无数据,待填充)
14:00 → (无数据,待填充)
15:00 → 28.0
FILL 算子处理:
扫描连续 NULL 窗口
找到边界值:前=25.0(12:00), 后=28.0(15:00)
线性插值:
13:00 → 25 + (28-25) × 1/3 = 26.0
14:00 → 25 + (28-25) × 2/3 = 27.0
输出:
12:00 → 25.0
13:00 → 26.0
14:00 → 27.0
15:00 → 28.0
FILL 模式对比:
FILL(NULL) → 13:00=NULL, 14:00=NULL
FILL(VALUE, 0) → 13:00=0, 14:00=0
FILL(PREV) → 13:00=25.0, 14:00=25.0
FILL(NEXT) → 13:00=28.0, 14:00=28.0
FILL(LINEAR) → 13:00=26.0, 14:00=27.0
FILL(NONE) → 跳过这两行
6. Project 算子
Project 算子的职责:
① 列选择(裁剪):
SELECT ts, current FROM meters
→ 只保留 ts 和 current 列
② 表达式计算:
SELECT ts, current * voltage AS power FROM meters
→ 计算 power = current × voltage
③ 列别名:
SELECT current AS amperage FROM meters
→ 输出列名改为 amperage
④ 函数应用:
SELECT ts, ROUND(current, 2) FROM meters
→ 应用 ROUND 函数
⑤ CASE WHEN:
SELECT ts, CASE WHEN current > 10 THEN 'HIGH' ELSE 'LOW' END
FROM meters
→ 条件分支
7. 表达式向量化执行
表达式向量化:
SELECT current * voltage AS power FROM meters
传统行式:
for each row:
output[i] = row.current * row.voltage
→ 每行一次乘法
向量化:
一次性取 4096 行的 current 列 → 数组 a[4096]
一次性取 4096 行的 voltage 列 → 数组 b[4096]
向量乘法:c[i] = a[i] * b[i](可用 SIMD)
→ 一次操作处理多行
优势:
- SIMD 指令加速(AVX/SSE)
- CPU 缓存友好
- 函数调用开销摊薄
8. DISTINCT 与 SELECT 的关系
DISTINCT 实现:
SELECT DISTINCT location FROM meters;
执行:
HashAgg (group by location)
→ 哈希表去重
→ 输出唯一值
→ 本质是 GROUP BY 但不带聚合函数
对比:
SELECT DISTINCT a, b → 等价于 GROUP BY a, b
SELECT COUNT(DISTINCT a) → 用 HyperLogLog(近似)或全去重
代码示例
排序场景
-- ✓ 高效:按 ts 排序(天然有序)
SELECT * FROM meters WHERE ts > now-1h ORDER BY ts DESC LIMIT 100;
-- ✓ 高效:TopN
SELECT * FROM meters ORDER BY current DESC LIMIT 10;
-- ✗ 低效:全表按非 ts 列排序
SELECT * FROM meters ORDER BY current; -- 没有 LIMIT
FILL 应用
-- 监控图表绘图(必须连续点)
SELECT _wstart, AVG(current)
FROM meters
WHERE ts > now-1h
INTERVAL(1m) FILL(LINEAR);
-- 数据归档(保留 NULL 标识)
SELECT _wstart, SUM(quantity)
FROM orders
WHERE ts > now-1d
INTERVAL(1h) FILL(NULL);
-- 累计监控(缺失用前值)
SELECT _wstart, MAX(counter)
FROM monotonic_counter
INTERVAL(1m) FILL(PREV);
投影表达式
-- 计算列
SELECT ts,
current * voltage AS power,
CASE WHEN current > 10 THEN 'HIGH' ELSE 'LOW' END AS level
FROM meters;
-- 时间格式化
SELECT TO_CHAR(ts, 'yyyy-mm-dd HH:MI'), current
FROM meters;
-- 嵌套函数
SELECT ROUND(AVG(current), 2) AS avg_c
FROM meters
INTERVAL(1h);
性能考量
排序性能
| 场景 | 复杂度 | 备注 |
|---|---|---|
| ORDER BY ts | O(N) | 利用天然有序 |
| ORDER BY ts + LIMIT | O(LIMIT) | 早停优化 |
| ORDER BY 其他列 | O(N log N) | 需要实际排序 |
| 内存放不下 | O(N log N) | 外排,慢得多 |
| 跨 VGroup | O(N log N) | + MergeExchange |
FILL 性能
FILL 算子本身开销极低。但 FILL(LINEAR) 需要"先看到后续值"才能填充,会增加少量延迟。FILL(PREV) 是最快的(无需前瞻)。
FAQ
Q1: ORDER BY ts 为什么不算开销?
数据存储天然按 ts 有序,Scan 算子按顺序读取即可。ORDER BY ts ASC 是默认行为,无需额外算子。
Q2: 大表 SELECT * ORDER BY ts 会爆内存吗?
如果没有 LIMIT,会传输全量数据。本身不爆内存(流式),但客户端可能 OOM。务必加 LIMIT。
Q3: FILL 会拖慢查询吗?
正常情况几乎无感知。性能开销主要在窗口聚合阶段,FILL 只是后处理。
Q4: SELECT 中能用窗口聚合的别名吗?
SELECT AVG(current) AS avg_c FROM meters WHERE avg_c > 10;
-- ✗ 错误:WHERE 在聚合前执行,看不到 avg_c
SELECT AVG(current) AS avg_c FROM meters HAVING avg_c > 10;
-- ✓ 正确:HAVING 在聚合后执行
参考
系统构架篇
- 01-《TDengine 整体架构全景》
- 02-《集群拓扑深度解析》
- 03-《MNode 内部机制深度解析》
- 04-《RPC 通信层深度解析》
- 05-《VNode 生命周期》
- 06-《RAFT 共识协议》
- 07-《端到端的消息流》
数据模型
- 01-《数据库创建与参数详解》
- 02-《超级表/子表/普通表》
- 03-《支持数据类型深度解析》
- 04-《TDengine Tag 设计哲学与 Schema 变更机制》
- 05-《TDengine 虚拟表实现原理》
存储引擎
- 01-《TDengine 存储引擎概览》
- 02-《TDengine MemTable 深度解析》
- 03-《TDengine WAL 预写日志机制》
- 04-《TDengine 数据文件格式》
- 05-《TDengine Commit 与 Flush 机制 》
- 06-《TDengine Compaction 合并策略 》
- 07-《TDengine 数据保留与 TTL》
- 08-《TDengine 压缩编码机制》
- 09-《TDengine Cache 与 Last 查询加速》
- 10-《TDengine 逻辑计划生成》
查询引擎
- 01-《TDengine 查询引擎概览》
- 02-《TDengine SQL 解析与词法分析》
- 03-《TDengine 语义分析与 AST 重写》
- 04-《TDengine 逻辑计划生成》
- 05-《TDengine 物理计划生成》
- 06-《TDengine 扫描算子》
- 07-《TDengine 聚合算子》
- 08-《TDengine 聚合算子》
- 09-《TDengine 连接算子》
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。

251

被折叠的 条评论
为什么被折叠?



