TDengine TSMA(时间范围小型物化聚合)完全指南:创建、查询优化与限制
【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine
导读
在 TDengine 中,当历史数据量持续增长、查询时间跨度扩展到天、月甚至年级别时,聚合查询需要扫描的原始数据块数量会急剧增加,查询耗时随之上升。TSMA(Time-Range Small Materialized Aggregates,时间范围小型物化聚合)允许用户为超表(supertable)或普通表(basic table)指定固定时间窗口,将窗口内的聚合结果预先计算并存储,使后续查询直接读取预计算结果而无需扫描原始数据。本文基于 01-tsma.md 文档,结合 TDengine 开源仓库源码,系统讲解 TSMA 的创建语法、支持的聚合函数、查询匹配规则、使用限制与相关配置参数,帮助你在实际业务中正确设计与使用 TSMA 加速长时间范围的聚合查询。
TSMA 是什么:从块内 SMA 到时间窗口物化
TDengine 的数据存储按数据块(block)组织,每个数据块内部本身就携带 SMA(Small Materialized Aggregates,小型物化聚合)信息,例如块内某列的最小值、最大值、和等。当查询时间范围较小、只覆盖少量数据块时,直接利用块内 SMA 即可显著减少扫描量。
但当查询时间范围达到天、月甚至年时,涉及的数据块数量非常庞大,块内 SMA 的粒度过小,无法有效加速查询。TSMA 正是为解决这一问题而生:它支持用户显式指定一个时间窗口(例如 5 分钟、1 小时、1 天),把窗口内的数据预先聚合,并将计算结果落盘存储。后续查询在窗口完全对齐的情况下,可以直接从预计算结果中取值,从而大幅提升查询性能。
TSMA 的计算结果以一张用户不可见的超表形式存放在原表所在的库中,该结果表无法被用户直接删除,会在执行DROP TSMA时自动清理。
创建 TSMA
基本语法
-- 基于超表或普通表创建 TSMA CREATE TSMA tsma_name ON [dbname.]table_name FUNCTION (func_name(func_param) [, ...] ) INTERVAL(time_duration); -- 基于更小窗口的 TSMA 创建更大窗口的 TSMA(递归 TSMA) CREATE RECURSIVE TSMA tsma_name ON [db_name.]tsma_name1 INTERVAL(time_duration); time_duration: number unit两种创建方式的核心差异在于:
- 直接创建:指定 TSMA 名称、目标表名、聚合函数列表和窗口大小;
- 递归创建(RECURSIVE):基于一个已存在的 TSMA 创建窗口更大的新 TSMA,此时不能指定
FUNCTION(),新 TSMA 继承源 TSMA 的函数列表;指定的INTERVAL必须是源 TSMA 窗口长度的整数倍,且存在额外约束——小时级只能基于 1h(不能基于 2h、3h),月级只能基于 1d(不能基于 2d、3d)。
从语法解析层面看,TDengine 在 source/libs/parser/inc/sql.y#L1536-L1550 中实现了两类命令:cmd ::= CREATE TSMA ...与cmd ::= CREATE RECURSIVE TSMA ...,其中函数列表通过tsma_func_list(A) ::= FUNCTION NK_LP func_list(B) NK_RP解析,TSMA 名称tsma_name复用普通标识符NK_ID规则。
命名规则
TSMA 的命名规则与表名类似,最大长度为表名长度上限减去输出表后缀长度。表名长度上限为 193,输出表后缀为_tsma_res_stb_,因此 TSMA 名称最大长度为 178。
创建约束
- 仅支持基于超表和普通表创建,不能基于子表(subtable)创建。
- 函数列表只能包含受支持的聚合函数(见下文函数表),且每个函数的参数必须是单一参数——即使该函数原生支持多参数,此处也只能传一个;参数必须是普通列名,不能是标签列(tag column)。
- 函数列表中出现相同的函数与列组合会被去重,例如同时创建两个
avg(c1)时,只会计算并输出一份结果。 - TSMA 计算会把所有函数的中间结果输出到另一张超表,该表会包含原表的全部标签列,外加 4 个附加列:
_wstart(窗口开始时间)、_wend(窗口结束时间)、_wduration(窗口时长),以及一个新标签列tbname。 - 函数数量上限:函数个数最多为「表的列数上限(含标签列)减去 TSMA 计算占用的 4 个附加列,再减去原表的标签列数」。超过该限制会报
Too many columns错误。 - 行宽限制:TSMA 输出本身是一张超表,其行长度受最大行长度限制。不同函数的中间结果大小不一,通常大于原始数据大小。若输出表行长度超限,会报
Row length exceeds max length错误,此时应减少函数数量,或将常用函数拆分成多个 TSMA。 - 窗口大小限制:
[1m ~ 1y/12n],其中INTERVAL的单位与查询中INTERVAL子句的单位一致,参见 时间单位说明。 - 全局唯一与数量上限:TSMA 是库内对象,但其名称在整个集群内全局唯一。集群内可创建的 TSMA 总数受服务端参数
maxTsmaNum限制。由于 TSMA 的后台计算基于流式计算(stream computing),每个 TSMA 创建时都会占用一个流,因此可创建的 TSMA 数量同时受当前已有流数量与流上限的制约。
maxTsmaNum在 source/common/src/tglobal.c#L1179 中注册,定义如下:
cfgAddInt32(pCfg, "maxTsmaNum", tsMaxTsmaNum, 0, 10, CFG_SCOPE_SERVER, CFG_DYN_SERVER, CFG_CATEGORY_GLOBAL, CFG_PRIV_SYSTEM);即默认值为 10,取值范围[0, 10],属于服务端全局配置且支持动态调整(CFG_DYN_SERVER)。
支持的聚合函数列表
| 函数 | 说明 |
|---|---|
| min | 最小值 |
| max | 最大值 |
| sum | 求和 |
| first | 窗口内首条记录值 |
| last | 窗口内末条记录值 |
| avg | 平均值 |
| count | 若需使用count(*),应创建count(ts)函数 |
| spread | 极差(最大值与最小值之差) |
| stddev | 标准差 |
注意count的使用方式:TSMA 中不能直接创建count(*),而应创建count(ts)来表达“统计记录数”的语义。
删除 TSMA
DROP TSMA [db_name.]tsma_name;若待删除的 TSMA 被其他 TSMA 作为基础(存在递归 TSMA 依赖),删除操作会报错:Invalid drop base tsma, drop recursive tsma first。因此必须先删除所有基于它创建的递归 TSMA,再删除基础 TSMA。
DROP TSMA与CREATE TSMA、SHOW CREATE TSMA一起被纳入权限体系管理,见 source/common/src/tpriv.c#L231-L234,其中DROP TSMA对应PRIV_CM_DROP权限。
TSMA 计算原理
TSMA 的计算通过流式计算完成,是一个后台异步过程:
- 计算结果不保证实时精确,但能保证最终正确性(ultimate correctness);
- 当原表的某个子表没有数据时,可能不会创建对应的输出子表。因此即使配置了
countAlwaysReturnValue,count查询也不会返回该表的计数结果; - 当存在大量历史数据时,创建 TSMA 后,流式计算会先回算历史数据,此期间新建的 TSMA 不会被查询使用;
- 数据发生更新、删除或数据过期时,受影响的数据会被自动重新计算;重算期间,TSMA 查询结果不保证实时精确。
若希望查询实时数据,有两种途径绕过 TSMA:
- 在 SQL 中增加 Hint:
/*+ skip_tsma() */; - 将客户端参数
querySmaOptimize设置为0。
相关配置参数
服务端参数:maxTsmaNum
- 位置:taosd 服务端配置,见 taosd 配置参数;
- 默认值:10;取值范围:
[0, 10]; - 作用:限制整个集群内可创建的 TSMA 总数。
客户端参数
以下参数均在 taosc 客户端配置中,详见 taosc 查询相关配置:
| 参数 | 说明 | 默认值 | 取值范围 |
|---|---|---|---|
querySmaOptimize | 查询时是否使用 TSMA:1使用预计算结果,0查询原始数据 | 0 | 0/1 |
maxTsmaCalcDelay | 单位秒,控制可接受的 TSMA 计算延迟。当 TSMA 计算进度与最新时间之差在该值范围内时使用该 TSMA,否则不使用 | 600(10 分钟) | 最小 600(10 分钟),最大 86400(1 天) |
tsmaDataDeleteMark | 单位毫秒,与流式计算参数deleteMark一致,控制流式计算中中间结果的保留时长 | 1d(86400000 ms) | 最小1h(3600000 ms) |
tsmaDataDeleteMark的语义需要特别关注:距离最后一条数据超过该时长的历史窗口不会保留中间结果。如果这些历史窗口中的数据被修改,TSMA 结果可能得不到更新,从而与查询原始数据的结果不一致。
这三个客户端参数在 source/common/src/tglobal.c 中均有注册与默认值定义:
// L774: querySmaOptimize, client, 0 ~ 1 cfgAddInt32(pCfg, "querySmaOptimize", tsQuerySmaOptimize, 0, 1, CFG_SCOPE_CLIENT, CFG_DYN_CLIENT, ...); // L854: maxTsmaCalcDelay, client, 600 ~ 86400 cfgAddInt32(pCfg, "maxTsmaCalcDelay", tsMaxTsmaCalcDelay, 600, 86400, CFG_SCOPE_CLIENT, ...); // L856: tsmaDataDeleteMark, client, 60*60*1000 ~ INT64_MAX, 默认 86400000ms cfgAddInt32(pCfg, "tsmaDataDeleteMark", tsmaDataDeleteMark, 60 * 60 * 1000, INT64_MAX, ...);同时,tsmaDataDeleteMark在查询解析阶段会被转换为与表时间精度一致的单位参与计算,见 source/libs/parser/src/parTranslater.c#L29057。
使用 TSMA 进行查询
窗口匹配与选择策略
TSMA 中定义的聚合函数可以直接用于大多数查询场景:
- 未指定窗口大小的查询:默认优先使用「包含查询中全部聚合函数」且窗口最大的 TSMA。例如
SELECT COUNT(*) FROM stable GROUP BY tbname会使用包含count(ts)且窗口最大的 TSMA。因此,如果聚合查询使用频繁,应尽量创建大窗口的 TSMA。 - 指定窗口大小的查询:即查询语句带
INTERVAL时,使用最大的、可被整除(divisible)的窗口 TSMA。在窗口查询中,INTERVAL、OFFSET、SLIDING三个值都会影响可用 TSMA 窗口大小——可被整除的窗口指 TSMA 窗口大小能被查询语句的INTERVAL, OFFSET, SLIDING整除。因此,若窗口查询使用频繁,创建 TSMA 时应综合考虑常用查询窗口、offset 与 sliding 的大小。
示例 1:创建了5m和10m两个窗口的 TSMA 后:
- 查询
INTERVAL(30m)时,优先使用10m的 TSMA; - 查询
INTERVAL(30m, 10m) SLIDING(5m)时,只有5m的 TSMA 能被使用。
多窗口存在时,优先选择窗口更大的 TSMA;未封口(unsealed)的窗口会通过查询更小窗口的 TSMA 或原始数据来计算。若某个场景完全无法使用 TSMA,整个查询会退回原始数据计算。
查询匹配示例
下面结合文档中的完整示例说明匹配与不匹配的情形。假设:
CREATE TSMA tsma1 ON stable FUNCTION(COUNT(ts), SUM(c1), SUM(c3), MIN(c1), MIN(c3), AVG(c1)) INTERVAL(1m);| 查询 | 是否使用 tsma1 | 原因 |
|---|---|---|
SELECT COUNT(*), SUM(c1) + SUM(c3) FROM stable; | ✅ 使用 | 函数均被定义 |
SELECT COUNT(*), AVG(c1) FROM stable GROUP/PARTITION BY tbname, tag1, tag2; | ✅ 使用 | 按标签分组不受影响 |
SELECT COUNT(*), MIN(c1) FROM stable INTERVAL(1h); | ✅ 使用 | 1h 是 1m 的整数倍 |
SELECT COUNT(*), MIN(c1), SPREAD(c1) FROM stable INTERVAL(1h); | ❌ 不可用 | spread未定义(即使 spread 可由已定义的 MIN/MAX 推算,也不允许) |
SELECT COUNT(*), MIN(c1) FROM stable INTERVAL(30s); | ❌ 不可用 | 窗口不匹配,查询窗口需为创建窗口的整数倍 |
SELECT COUNT(*), MIN(c1) FROM stable where c2 > 0; | ❌ 不可用 | WHERE 含普通列过滤 |
SELECT COUNT(*) FROM stable GROUP BY c2; | ❌ 不可用 | GROUP BY 含普通列 |
SELECT MIN(c3), MIN(c2) FROM stable INTERVAL(1m); | ❌ 不可用 | c2未在 tsma1 中定义 |
递归 TSMA 的查询效果
基于 tsma1 创建递归 TSMA:
CREATE RECURSIVE TSMA tsma2 on tsma1 INTERVAL(1h);SELECT COUNT(*), SUM(c1) FROM stable;—— 使用 tsma2(窗口更大优先);SELECT COUNT(*), AVG(c1) FROM stable GROUP/PARTITION BY tbname, tag1, tag2;—— 使用 tsma2;SELECT COUNT(*), MIN(c1) FROM stable INTERVAL(2h);—— 使用 tsma2(2h 是 1h 的整数倍);SELECT COUNT(*), MIN(c1) FROM stable WHERE ts < '2023-01-01 10:10:10' INTERVAL(30m);—— 使用 tsma1(时间条件只作用于主键时间列 ts,30m 窗口匹配 1m 的 tsma1);SELECT COUNT(*), MIN(c1) + MIN(c3) FROM stable INTERVAL(30m);—— 使用 tsma1(30m 不能整除 1h,但能整除 1m);SELECT COUNT(*), MIN(c1) FROM stable INTERVAL(1h) SLIDING(30m);—— 使用 tsma1(SLIDING(30m) 无法被 1h 整除,只能匹配 1m 窗口);SELECT COUNT(*), MIN(c1), SPREAD(c1) FROM stable INTERVAL(1h);—— 不可用(spread 未定义);SELECT COUNT(*), MIN(c1) FROM stable INTERVAL(30s);—— 不可用(窗口不匹配);SELECT COUNT(*), MIN(c1) FROM stable where c2 > 0;—— 不可用(普通列过滤)。
查询限制(querySmaOptimize = 1 时)
当querySmaOptimize为1且 SQL 中没有skip_tsma()hint 时,以下场景无法使用 TSMA:
- TSMA 中定义的聚合函数未覆盖当前查询的函数列表;
- 非
INTERVAL窗口,或查询窗口大小(含INTERVAL, SLIDING, OFFSET)不是定义窗口的整数倍。例如定义窗口为 2m,查询使用 5 分钟窗口则无法命中,但若存在 1m 窗口则可以使用; WHERE条件中包含任何普通列(非主键时间列)的过滤;PARTITION或GROUP BY中包含任何普通列或其表达式;- 存在其他更快的优化逻辑可先执行,例如 last 缓存优化。执行顺序为先尝试 last 优化,last 无法进行时再判断能否使用 TSMA 优化;
- 当前 TSMA 的计算进度延迟超过
maxTsmaCalcDelay参数阈值。
对原表的操作限制
创建 TSMA 后,对原超表进行以下操作会受到限制:
- 删表:必须先删除该表上的全部 TSMA,才能删除表;
- 标签列:原表的所有标签列不能被删除,子表的标签列名与标签值也不能被修改;删除标签列前必须先删除 TSMA;
- 数据列:被某个 TSMA 使用到的列不能被删除,必须先删除 TSMA。新增列不受影响,但新列不会自动加入任何已有 TSMA——若需对新列计算,必须另行创建 TSMA。
查看 TSMA
SHOW [db_name.]TSMAS; SELECT * FROM information_schema.ins_tsma;两种方式均可查看 TSMA 定义。需要注意:如果创建时指定的函数数量较多且列名较长,函数列表的展示可能被截断(当前支持最大输出 256KB)。
总结与最佳实践建议
综合文档与源码实现,使用 TSMA 加速长时间范围聚合查询的关键要点可归纳为:
- 按查询模式设计窗口:频繁执行无窗口聚合查询时,优先创建大窗口 TSMA;频繁执行
INTERVAL/SLIDING窗口查询时,要让查询窗口、offset、sliding 尽量成为 TSMA 窗口的整数倍,并用递归 TSMA 构建「1m → 1h → 1d」式的多级窗口体系,让优化器始终有最合适的窗口可用; - 函数列表宁全勿滥:函数列表决定可被优化的查询集合,
spread、多列组合等未定义的函数会直接导致整个查询退回原始数据;同时受列数上限与行宽上限约束,常用函数可拆分到多个 TSMA; - 关注数据一致性语义:TSMA 是异步流式计算结果,只保证最终一致性。对实时性敏感或涉及历史窗口数据更新的场景,应使用
/*+ skip_tsma() */hint 或设置querySmaOptimize = 0,并合理配置maxTsmaCalcDelay与tsmaDataDeleteMark; - 遵守运维顺序:删除表、标签列或被 TSMA 使用的列之前,必须先删除对应 TSMA;删除递归 TSMA 必须先于其基础 TSMA。
通过上述设计,TSMA 可以在天、月、年级别的时间范围内,让聚合查询直接命中预计算结果,显著减少原始数据扫描量,是 TDengine 上长周期统计分析类查询的关键性能优化手段。
【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考