TSMA (窗口预聚集) 用户手册
一、什么是 TSMA
TSMA(Time-Range Small Materialized Aggregates,时间窗口预聚集)是 TDengine 提供的一种查询加速机制 。其核心思想是:将常用的聚合计算结果按固定时间窗口提前算好并存储下来,查询时直接读取预计算结果,而不需要扫描海量原始数据。
1.1 类比理解
想象你经营一家连锁商店,每天产生数百万条销售流水记录。老板每周一问:"上周每天的总销售额是多少?"
- 不使用 TSMA:每次都从数百万条流水中逐条累加,耗时数分钟。
- 使用 TSMA:系统后台每小时/每天自动算好汇总结果。老板一问,直接读取现成的汇总数字,瞬间返回。
1.2 工作原理
原始数据 (海量时序记录)
┌──────────────────────────────────────────┐
│ ts, 设备ID, 温度, 电压, 电流 ... │
│ 数亿行数据 │
└──────────────────────────────────────────┘
│
│ TSMA 后台自动计算 (基于流计算引擎)
▼
预聚集结果表 (按固定时间窗口汇总)
┌──────────────────────────────────────────┐
│ _wstart │ avg(温度) │ max(电压) │... │
│ 2024-01-01 │ 36.5 │ 220.3 │ ... │
│ 2024-01-02 │ 37.1 │ 221.0 │ ... │
└──────────────────────────────────────────┘
│
│ 查询优化器自动路由
▼
用户查询: SELECT avg(温度) FROM meters WHERE ts > '2024-01-01'
→ 直接读预聚集结果,性能提升 10~100 倍
关键特性:
- 对用户透明:无需修改查询 SQL,优化器自动判断是否可以走 TSMA
- 异步计算:后台流计算引擎自动维护,不影响写入性能
- 最终一致性:数据更新或删除时自动重算受影响的窗口
- 分层递归:可基于小窗口 TSMA 创建更大窗口的 TSMA,进一步加速
二、适用场景
✅ 推荐使用的场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 大时间跨度聚合查询 | 查询天、周、月、年级别汇总 | 查询过去一年的每月平均温度 |
| 监控大盘/报表 | 定期产出固定格式的统计报表 | 设备运行日报、能耗周报 |
| 固定窗口的 INTERVAL 查询 | 查询窗口为 TSMA 窗口整数倍 | 每小时/每天的聚合统计 |
| 高并发聚合查询 | 多用户频繁执行相同类型查询 | 数据看板、BI 工具对接 |
| 按设备/标签分组聚合 | GROUP BY tbname / tag 列 | 每个设备的日均温度 |
❌ 不适合的场景
| 场景 | 原因 |
|---|---|
| 按普通列过滤后聚合 | WHERE 条件包含非时间列过滤时无法使用 TSMA |
| 按普通列 GROUP BY | PARTITION/GROUP BY 包含非标签列时无法使用 |
| 非标准窗口查询 | SESSION、STATE_WINDOW 等非 INTERVAL 窗口不支持 |
| 写入远大于读取 | TSMA 会增加后台计算开销,若极少查询则不划算 |
| 数据频繁更新 | 频繁更新会触发大量窗口重算 |
三、快速上手
3.1 前置条件
sql
-- 1. 创建 snode(流计算节点),TSMA 依赖流计算引擎
CREATE SNODE ON DNODE 1;
3.2 准备示例数据
sql
-- 创建数据库
CREATE DATABASE power VGROUPS 2;
USE power;
-- 创建超级表:智能电表
CREATE TABLE meters (
ts TIMESTAMP,
voltage INT, -- 电压
current FLOAT, -- 电流
power FLOAT, -- 功率
temp FLOAT -- 温度
) TAGS (
location VARCHAR(64), -- 位置
group_id INT -- 分组
);
-- 创建子表并写入数据
CREATE TABLE d1001 USING meters TAGS('北京', 1);
CREATE TABLE d1002 USING meters TAGS('上海', 1);
CREATE TABLE d1003 USING meters TAGS('广州', 2);
-- 插入模拟数据(实际场景中数据量为千万至亿级)
INSERT INTO d1001 VALUES
('2024-01-01 00:00:00', 220, 10.5, 2310, 25.1),
('2024-01-01 00:05:00', 221, 10.3, 2276, 25.3),
('2024-01-01 00:10:00', 219, 10.8, 2365, 25.0),
('2024-01-01 01:00:00', 222, 10.1, 2242, 25.5),
('2024-01-01 02:00:00', 218, 10.9, 2376, 24.8);
INSERT INTO d1002 VALUES
('2024-01-01 00:00:00', 225, 11.0, 2475, 26.0),
('2024-01-01 00:05:00', 224, 11.2, 2509, 26.1),
('2024-01-01 01:00:00', 223, 10.8, 2408, 25.8);
INSERT INTO d1003 VALUES
('2024-01-01 00:00:00', 230, 12.0, 2760, 27.0),
('2024-01-01 01:00:00', 228, 11.5, 2622, 26.5);
3.3 创建 TSMA
sql
-- 创建 10 分钟窗口的 TSMA
CREATE TSMA tsma_10m ON power.meters
FUNCTION(avg(voltage), max(voltage), min(voltage), avg(current), sum(power), count(ts), avg(temp))
INTERVAL(10m);
-- 创建 1 小时窗口的递归 TSMA(基于 10 分钟 TSMA 构建)
CREATE RECURSIVE TSMA tsma_1h ON power.tsma_10m INTERVAL(1h);
⏳ 创建后需等待后台流计算完成历史数据的预聚集(可通过
EXPLAIN确认是否已启用)。
3.4 自动加速查询
创建 TSMA 后,无需修改任何查询 SQL,优化器自动使用 TSMA:
sql
-- 查询 1:全表聚合 → 自动使用最大窗口 tsma_1h
SELECT avg(voltage), max(voltage) FROM power.meters;
-- 查询 2:按设备分组 → 自动使用 tsma_1h
SELECT avg(voltage), tbname FROM power.meters GROUP BY tbname;
-- 查询 3:每小时统计 → 自动使用 tsma_1h
SELECT avg(voltage), _wstart FROM power.meters INTERVAL(1h);
-- 查询 4:每 30 分钟统计 → 自动使用 tsma_10m(30 是 10 的整数倍)
SELECT avg(voltage), _wstart FROM power.meters INTERVAL(30m);
-- 查询 5:带时间范围 → 边界部分查原始数据,中间部分使用 TSMA
SELECT avg(voltage) FROM power.meters
WHERE ts >= '2024-01-01 00:03:00' AND ts < '2024-01-01 02:00:00';
3.5 验证是否使用了 TSMA
sql
-- 使用 EXPLAIN 查看执行计划
EXPLAIN VERBOSE TRUE SELECT avg(voltage) FROM power.meters INTERVAL(1h);
-- 输出中若出现 "Table Scan on tsma_1h_tsma_res_stb_" 字样,
-- 即表示查询已走 TSMA 优化路径
四、深入理解 TSMA 的选择策略
优化器按以下规则选择 TSMA:
4.1 无 INTERVAL 的聚合查询
策略:选择包含所有查询函数的最大窗口 TSMA。
sql
-- 假设已创建:tsma_10m(avg,max,min,count) 和 tsma_1h(递归,同函数列表)
SELECT avg(voltage), count(ts) FROM meters;
-- → 使用 tsma_1h(最大窗口,函数列表匹配)
SELECT avg(voltage), sum(power) FROM meters;
-- → 使用 tsma_1h(sum(power) 在函数列表中)
SELECT avg(voltage), spread(voltage) FROM meters;
-- → 不使用任何 TSMA(spread 未在函数列表中定义,
-- 即使 spread 可由 min + max 推导,但 TSMA 不做此推导)
4.2 有 INTERVAL 的窗口查询
策略:选择查询 INTERVAL 能整除的最大 TSMA 窗口。
sql
-- tsma_10m(INTERVAL=10m), tsma_1h(INTERVAL=1h)
SELECT avg(voltage) FROM meters INTERVAL(30m);
-- → 使用 tsma_10m(30 ÷ 10 = 3,整除 ✓)
-- → tsma_1h 不可用(30 ÷ 60 不整除 ✗)
SELECT avg(voltage) FROM meters INTERVAL(2h);
-- → 使用 tsma_1h(120 ÷ 60 = 2,整除 ✓)
SELECT avg(voltage) FROM meters INTERVAL(7m);
-- → 不使用 TSMA(7 ÷ 10 不整除 ✗)
4.3 带 SLIDING 和 OFFSET 的查询
SLIDING 和 OFFSET 也必须是 TSMA 窗口的整数倍。
sql
SELECT avg(voltage) FROM meters INTERVAL(1h) SLIDING(30m);
-- → 使用 tsma_10m(INTERVAL=60m, SLIDING=30m,均为 10 的整数倍)
SELECT avg(voltage) FROM meters INTERVAL(1h, 25m) SLIDING(25m);
-- → 使用 tsma_10m 的 5m 版本(若有)
-- → 因为 OFFSET=25m 和 SLIDING=25m 必须都能被 TSMA 窗口整除
4.4 带时间范围的查询(分段扫描)
当查询时间范围的边界不对齐 TSMA 窗口时,优化器会自动分段:
sql
SELECT avg(voltage) FROM meters
WHERE ts >= '2024-01-01 00:03:00' AND ts < '2024-01-01 02:30:00'
INTERVAL(30m);
执行计划分为三段:
| 时间段 | 数据源 | 原因 |
|---|---|---|
| 00:03:00 ~ 00:09:59 | 原始表 | 起始时间未对齐 10m 窗口边界 |
| 00:10:00 ~ 01:59:59 | tsma_10m | 完整的 TSMA 窗口范围 |
| 02:00:00 ~ 02:30:00 | 原始表 | 结束部分未满窗口(或可用更小的 TSMA) |
五、性能最佳实践
5.1 TSMA 窗口设计原则
业务层面 TSMA 设计
┌─────────────────┐ ┌──────────────────┐
│ 秒级聚合很少用 │ │ 不必创建秒级 TSMA │
│ 分钟级报表常见 │ ───────→ │ 创建 1m 或 5m TSMA│
│ 小时/天级别最多 │ │ 递归创建 1h / 1d │
│ 月/年级别偶尔用 │ │ 递归创建 1n / 1y │
└─────────────────┘ └──────────────────┘
核心建议:
-
基础窗口取最小公约数
分析业务中常用的查询窗口,以它们的最大公约数作为基础 TSMA 窗口。例如常查 10m、30m、1h,则基础窗口用 10m。
-
逐层递归创建大窗口
sql-- 层级 1:基础窗口(最小粒度) CREATE TSMA tsma_base ON db.stb FUNCTION(...) INTERVAL(10m); -- 层级 2:中等窗口 CREATE RECURSIVE TSMA tsma_1h ON db.tsma_base INTERVAL(1h); -- 层级 3:大窗口 CREATE RECURSIVE TSMA tsma_1d ON db.tsma_1h INTERVAL(1d);⚠️ 递归 TSMA 的 INTERVAL 必须是基础 TSMA 的整数倍,且天只能基于 1h 建立,月只能基于 1d 建立。
-
函数列表一次性覆盖
基础 TSMA 的函数列表应包含所有业务需要的聚合函数,递归 TSMA 自动继承同一函数列表。
sql-- ✅ 好的做法:一次包含所有常用函数 CREATE TSMA tsma_base ON db.meters FUNCTION(count(ts), avg(voltage), max(voltage), min(voltage), sum(power), avg(current), first(temp), last(temp)) INTERVAL(5m); -- ❌ 不好的做法:拆分为多个小 TSMA(浪费流计算资源) CREATE TSMA tsma_a ON db.meters FUNCTION(avg(voltage)) INTERVAL(5m); CREATE TSMA tsma_b ON db.meters FUNCTION(max(voltage)) INTERVAL(5m); -
注意 count(*) 的写法
TSMA 不支持
count(*),应创建count(ts)替代,查询时count(*)会自动匹配。
5.2 典型业务场景配置方案
场景 A:IoT 设备监控大盘
sql
-- 采集频率:每秒一条,1000 台设备
-- 大盘展示:实时(最近 5 分钟)、小时级、天级
CREATE TSMA tsma_5m ON iot.sensors
FUNCTION(avg(temp), max(temp), min(temp), avg(humidity),
count(ts), last(ts), sum(power))
INTERVAL(5m);
CREATE RECURSIVE TSMA tsma_1h ON iot.tsma_5m INTERVAL(1h);
CREATE RECURSIVE TSMA tsma_1d ON iot.tsma_1h INTERVAL(1d);
-- 大盘查询自动加速:
-- "最近 24 小时每小时平均温度" → tsma_1h
-- "过去 30 天每天最高温度" → tsma_1d
-- "所有设备的总功耗" → tsma_1d
场景 B:能耗月报
sql
-- 需求:每月出一份按区域汇总的能耗报告
CREATE TSMA tsma_1h ON energy.meters
FUNCTION(sum(kwh), avg(kwh), max(kwh), count(ts))
INTERVAL(1h);
CREATE RECURSIVE TSMA tsma_1d ON energy.tsma_1h INTERVAL(1d);
CREATE RECURSIVE TSMA tsma_1n ON energy.tsma_1d INTERVAL(1n);
-- 月报查询:
SELECT sum(kwh), avg(kwh), location
FROM energy.meters
WHERE ts >= '2024-01-01' AND ts < '2024-02-01'
PARTITION BY location;
-- → 自动使用 tsma_1n
5.3 配置参数调优
| 参数 | 默认值 | 说明 | 调优建议 |
|---|---|---|---|
querySmaOptimize |
True |
是否启用 TSMA 优化 | 保持开启;调试时可临时关闭对比性能 |
maxTsmaCalcDelay |
600s | 允许的计算延迟 | 实时性要求高时减小;报表场景可增大到 86400 |
tsmaDataDeleteMark |
1d | 流计算中间结果保留时间 | 有历史数据更新需求时增大此值 |
maxTsmaNum |
10 | 集群最大 TSMA 数量 | 按需调整,范围 [0, 10] |
sql
-- 临时关闭 TSMA(用于对比查询性能)
ALTER LOCAL 'querySmaOptimize' '0';
-- 重新开启
ALTER LOCAL 'querySmaOptimize' '1';
-- 单条查询跳过 TSMA(使用 hint)
SELECT /*+ skip_tsma() */ avg(voltage) FROM meters;
六、SQL 语法参考
6.1 创建 TSMA
sql
-- 基于超级表或普通表创建
CREATE TSMA tsma_name ON [db_name.]table_name
FUNCTION(func_name(column) [, ...])
INTERVAL(time_duration);
-- 基于已有 TSMA 创建更大窗口(递归 TSMA)
CREATE RECURSIVE TSMA tsma_name ON [db_name.]base_tsma_name
INTERVAL(time_duration);
参数约束:
- INTERVAL 范围:
1m~1y(1分钟 ~ 1年) - 递归 TSMA 的 INTERVAL 必须是基础 TSMA 的整数倍
- 天(d)只能基于 1h 建立,月(n)只能基于 1d 建立
- TSMA 名称最大长度:178 个字符
- 函数参数只能是单个普通列名,不能是标签列或表达式
6.2 支持的聚合函数
| 函数 | 示例 | 备注 |
|---|---|---|
count |
count(ts) |
用 count(ts) 代替 count(*) |
avg |
avg(voltage) |
|
sum |
sum(power) |
|
min |
min(temp) |
|
max |
max(voltage) |
|
first |
first(temp) |
|
last |
last(ts) |
|
spread |
spread(voltage) |
|
stddev |
stddev(current) |
6.3 删除 TSMA
sql
DROP TSMA [db_name.]tsma_name;
若有递归 TSMA 依赖于当前 TSMA,必须先删除所有递归 TSMA。
6.4 查看 TSMA
sql
-- 查看指定库的 TSMA
SHOW [db_name.]TSMAS;
-- 查看所有 TSMA
SELECT * FROM information_schema.ins_tsma;
七、注意事项与限制
7.1 查询限制
以下情况无法使用 TSMA,查询将回退到扫描原始数据:
| 限制条件 | 示例 |
|---|---|
| 查询函数不在 TSMA 定义中 | TSMA 有 avg(c1),查询 spread(c1) |
| WHERE 过滤普通列 | WHERE voltage > 200 |
| GROUP BY 普通列 | GROUP BY voltage |
| 查询窗口不是 TSMA 的整数倍 | TSMA=10m,查询 INTERVAL(7m) |
| 使用非 INTERVAL 窗口 | SESSION、STATE_WINDOW |
| TSMA 计算延迟超出阈值 | 大量历史数据正在计算中 |
| 函数参数含表达式 | avg(c1 + c2) |
7.2 DDL 限制
创建 TSMA 后,原始表的以下操作受限:
- ❌ 不能删除被 TSMA 使用的列(需先删 TSMA)
- ❌ 不能删除/重命名 tag 列(需先删 TSMA)
- ❌ 不能修改子表 tag 值(需先删 TSMA)
- ❌ 不能删除有 TSMA 的表(需先删所有 TSMA)
- ❌ 不能直接删除 TSMA 关联的 stream(需先删 TSMA)
- ✅ 可以新增列(但新列不在已有 TSMA 中,需新建 TSMA)
7.3 数据一致性
- TSMA 计算是异步的 ,不保证实时性,但保证最终一致性
- 历史数据创建 TSMA 后需等待流计算完成,期间新 TSMA 不被使用
- 数据更新/删除会自动触发受影响窗口的重算
- 超出
tsmaDataDeleteMark时间范围的历史窗口修改不会更新 TSMA 结果
八、完整示例:从创建到验证
sql
-- ============ 第一步:准备环境 ============
CREATE SNODE ON DNODE 1;
CREATE DATABASE demo VGROUPS 2;
USE demo;
CREATE TABLE sensors (
ts TIMESTAMP, temp FLOAT, humidity FLOAT, pressure FLOAT
) TAGS (site VARCHAR(32), floor INT);
CREATE TABLE s1 USING sensors TAGS('A栋', 1);
CREATE TABLE s2 USING sensors TAGS('A栋', 2);
CREATE TABLE s3 USING sensors TAGS('B栋', 1);
-- 写入数据...
-- ============ 第二步:创建 TSMA 体系 ============
-- 基础层:5 分钟窗口
CREATE TSMA tsma_5m ON demo.sensors
FUNCTION(avg(temp), max(temp), min(temp), avg(humidity), count(ts))
INTERVAL(5m);
-- 中间层:1 小时窗口(递归)
CREATE RECURSIVE TSMA tsma_1h ON demo.tsma_5m INTERVAL(1h);
-- 顶层:1 天窗口(递归)
CREATE RECURSIVE TSMA tsma_1d ON demo.tsma_1h INTERVAL(1d);
-- ============ 第三步:查询自动加速 ============
-- 全局平均温度 → tsma_1d
SELECT avg(temp) FROM demo.sensors;
-- 每小时最高温度 → tsma_1h
SELECT max(temp), _wstart FROM demo.sensors INTERVAL(1h);
-- 按楼层每天平均湿度 → tsma_1d
SELECT avg(humidity), site, floor FROM demo.sensors
PARTITION BY site, floor INTERVAL(1d);
-- 指定时间范围查询 → 自动分段:边界走原始数据,中间走 TSMA
SELECT avg(temp) FROM demo.sensors
WHERE ts >= '2024-06-15 08:13:00' AND ts < '2024-06-16 00:00:00'
INTERVAL(1h);
-- ============ 第四步:验证 ============
-- 查看执行计划
EXPLAIN VERBOSE TRUE
SELECT avg(temp) FROM demo.sensors INTERVAL(1h);
-- 对比查询结果一致性
ALTER LOCAL 'querySmaOptimize' '1';
SELECT avg(temp) FROM demo.sensors INTERVAL(1h);
ALTER LOCAL 'querySmaOptimize' '0';
SELECT avg(temp) FROM demo.sensors INTERVAL(1h);
-- 两次结果应完全一致
-- ============ 清理 ============
-- 删除顺序:从顶层递归 TSMA 开始
DROP TSMA demo.tsma_1d;
DROP TSMA demo.tsma_1h;
DROP TSMA demo.tsma_5m;
九、常见问题
Q: 创建 TSMA 后查询没有加速?
A: 检查以下项:
(1) querySmaOptimize 是否为 True;
(2) 用 EXPLAIN 确认是否走了 TSMA;
(3) 流计算是否已完成历史数据处理(刚创建时需等待)。
Q: TSMA 和数据库的 Block SMA 有什么区别?
A: Block SMA 是存储引擎自动维护的每个数据块内的最小聚合信息,粒度很小。
TSMA 是用户定义的、按时间窗口的预计算,窗口可以从分钟到年,粒度更大,加速效果更显著。
Q: 能否对子表创建 TSMA?
A: 不能。TSMA 只能基于超级表或普通表创建,不支持子表。
Q: TSMA 占用多少存储空间?
A: TSMA 输出为一张超级表,每行存储聚合函数的中间结果(通常比原始数据大)。但由于行数大幅减少(原始数据行数 ÷ 窗口内行数),总体存储增量有限。
Q: 为什么 spread(c1) 不能使用定义了 min(c1) 和 max(c1) 的 TSMA?
A: TSMA 不做函数间的逻辑推导。即使 spread = max - min,也必须显式在函数列表中包含 spread(c1)。
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。