TDengine 常见问题 TOP3

TOP3 主题:流式计算(Stream)相关问题
典型表现:建流失败、结果不符预期、日志狂刷拖慢业务
为什么它是 TOP3
流式计算是 TDengine 最容易被"用错"的功能,也是社区讨论最密集的话题之一:
- 近 6 周社区里,
流计算相关主题密集出现:《流计算 sql 疑问》《3.4.1.0 流计算日志一直狂刷,导致其它业务受影响》《【流式计算】流式计算聚合各子表指标,数据全部一致是为啥》《官方给看看这流计算怎么执行不了,是 bug 吗》...... - 它有版本断层 :老版本用
TRIGGER关键字,新版本已改为INTERVAL/PERIOD/COUNT_WINDOW等语法,照抄旧博客/旧文档必然建流失败。 - 它的概念密度高:触发方式、窗口类型、分组、输出表、乱序、水位线、重算......任意一个理解偏差都会导致"建出来了但结果不对",而用户往往以为是 bug。
- 它的故障会外溢 :流任务异常时会刷大量日志、引发频繁重算,把 CPU/IO 打满,拖慢整个集群的普通 SQL。
所以这篇的重点是:先把"结果不对"和"建不出来"的症状分清,再补上概念短板,最后给出正确建流的模板。
一、症状大全(对号入座)
A. 建流失败 / 流不执行
| # | 报错 / 现象 | 指向 |
|---|---|---|
| A1 | Invalid stream query(0x80002645) |
流语句非法 |
| A2 | Stream not allowed(0x8000265A) |
用了流计算不支持的函数 |
| A3 | Stream already exists(0x800003F0)/ Stream not exist(0x800003F1) |
流已存在 / 不存在 |
| A4 | Too many streams(0x800003F6) |
流数量超限(集群级限制) |
| A5 | Stream temporarily does not support source db having replica > 1(0x800003F5) |
源库副本数 > 1 |
| A6 | Stream has no stored CREATE statement(0x800003F9) |
流由过低版本创建,元数据不兼容 |
| A7 | Stream output table name too long(0x80007014)/ Stream output table name calc failed(0x80007016) |
输出子表名规则算不出来或超长 |
| A8 | Stream rollup tag path is illegal(0x80004118) |
ROLLUP BY 标签路径非法 |
| A9 | Federated query is disabled for stream(0x8000411A) |
流引用了外部源表但未开联邦查询 |
| A10 | Snode not deployed(0x80000411)、Snode not found(0x80000410) |
集群没部署 snode |
| A11 | Snode already deployed(0x8000040F)/ Snode already exists(0x800003A4) |
重复部署 snode |
| A12 | 建流语法报错(near "TRIGGER" 之类) |
用了旧版 TRIGGER 语法 |
| A13 | 流建好了,但数据写进去完全没反应,查询结果表始终为空 | snode 缺失 / 触发条件不满足 |
| A14 | Stream task not exist(0x80004100) |
流任务丢失,需查服务端日志 |
B. 结果不符预期(最高频)
| # | 现象 | 指向 |
|---|---|---|
| B1 | 各子表的聚合结果完全一致 | 漏了 PARTITION BY tbname |
| B2 | 流计算导致数据重复插入多条 | 输出表设计 / 重复触发 |
| B3 | 窗口边界不对,_wstart / _wend 与预期不符 |
窗口偏移 / 时区问题 |
| B4 | TWA 等计算结果不符合预期 |
窗口与过滤条件用法问题 |
| B5 | 同一分组、同一时间戳的结果互相覆盖 | 输出表主键设计问题 |
| B6 | 部分计算结果凭空消失 | 结果主键为 NULL 被丢弃 |
| B7 | 历史数据没有被计算 | 未指定 FILL_HISTORY / FILL_HISTORY_FIRST |
| B8 | 计数窗口在开头无法正常衔接 | COUNT_WINDOW 未优先计算历史数据 |
| B9 | 业务边界被跨越(如订单切换时把两组数据配成一对) | 未用 STATE_WINDOW 划分业务边界 |
C. 日志与资源
| # | 现象 | 指向 |
|---|---|---|
| C1 | 流计算日志一直狂刷,CPU / IO wait 很高,应用无操作也一样 | 频繁重算 / 流任务异常 |
| C2 | 简单 SQL 执行都变慢 | 同上,snode 与 taosd 混部导致资源抢占 |
| C3 | ins_streams 中显示大量重算、进度落后、错误信息 |
乱序写入严重 |
| C4 | Rsma stream state open / commit(0x80003154 / 0x80003155) |
流算子状态存储失败 |
D. 维护操作类
| # | 报错 / 现象 | 指向 |
|---|---|---|
| D1 | Snode still in use with streams(0x80007007) |
还有流在用,不能删 snode |
| D2 | Db used by stream(0x8000700E) |
库被流引用,不能删 |
| D3 | Stream must be dropped first(0x800003F3) |
依赖对象不能先删 |
| D4 | DROP SNODE 失败 |
snode 或其副本不在线 |
| D5 | RECALCULATE STREAM 一直 Pending / Running |
重算在后台执行中,或区间不合法 |
E. 版本与语法断层
| # | 现象 | 指向 |
|---|---|---|
| E1 | 照抄文档/博客建流报语法错误 | TRIGGER 仅适用于 v3.0.0.0--v3.3.7.0 |
| E2 | 升级后原有流不工作 | 建流语句需要改造 |
| E3 | 流数量受限、提示授权不足 | Stream creation limited by license(0x80000807) |
二、原因分析
2.1 流计算的执行链路
#mermaid-svg-cQY2bDDgBgrmtOFC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cQY2bDDgBgrmtOFC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cQY2bDDgBgrmtOFC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cQY2bDDgBgrmtOFC .error-icon{fill:#552222;}#mermaid-svg-cQY2bDDgBgrmtOFC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cQY2bDDgBgrmtOFC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cQY2bDDgBgrmtOFC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cQY2bDDgBgrmtOFC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cQY2bDDgBgrmtOFC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cQY2bDDgBgrmtOFC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cQY2bDDgBgrmtOFC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cQY2bDDgBgrmtOFC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cQY2bDDgBgrmtOFC .marker.cross{stroke:#333333;}#mermaid-svg-cQY2bDDgBgrmtOFC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cQY2bDDgBgrmtOFC p{margin:0;}#mermaid-svg-cQY2bDDgBgrmtOFC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cQY2bDDgBgrmtOFC .cluster-label text{fill:#333;}#mermaid-svg-cQY2bDDgBgrmtOFC .cluster-label span{color:#333;}#mermaid-svg-cQY2bDDgBgrmtOFC .cluster-label span p{background-color:transparent;}#mermaid-svg-cQY2bDDgBgrmtOFC .label text,#mermaid-svg-cQY2bDDgBgrmtOFC span{fill:#333;color:#333;}#mermaid-svg-cQY2bDDgBgrmtOFC .node rect,#mermaid-svg-cQY2bDDgBgrmtOFC .node circle,#mermaid-svg-cQY2bDDgBgrmtOFC .node ellipse,#mermaid-svg-cQY2bDDgBgrmtOFC .node polygon,#mermaid-svg-cQY2bDDgBgrmtOFC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cQY2bDDgBgrmtOFC .rough-node .label text,#mermaid-svg-cQY2bDDgBgrmtOFC .node .label text,#mermaid-svg-cQY2bDDgBgrmtOFC .image-shape .label,#mermaid-svg-cQY2bDDgBgrmtOFC .icon-shape .label{text-anchor:middle;}#mermaid-svg-cQY2bDDgBgrmtOFC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cQY2bDDgBgrmtOFC .rough-node .label,#mermaid-svg-cQY2bDDgBgrmtOFC .node .label,#mermaid-svg-cQY2bDDgBgrmtOFC .image-shape .label,#mermaid-svg-cQY2bDDgBgrmtOFC .icon-shape .label{text-align:center;}#mermaid-svg-cQY2bDDgBgrmtOFC .node.clickable{cursor:pointer;}#mermaid-svg-cQY2bDDgBgrmtOFC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cQY2bDDgBgrmtOFC .arrowheadPath{fill:#333333;}#mermaid-svg-cQY2bDDgBgrmtOFC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cQY2bDDgBgrmtOFC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cQY2bDDgBgrmtOFC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cQY2bDDgBgrmtOFC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cQY2bDDgBgrmtOFC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cQY2bDDgBgrmtOFC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cQY2bDDgBgrmtOFC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cQY2bDDgBgrmtOFC .cluster text{fill:#333;}#mermaid-svg-cQY2bDDgBgrmtOFC .cluster span{color:#333;}#mermaid-svg-cQY2bDDgBgrmtOFC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cQY2bDDgBgrmtOFC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cQY2bDDgBgrmtOFC rect.text{fill:none;stroke-width:0;}#mermaid-svg-cQY2bDDgBgrmtOFC .icon-shape,#mermaid-svg-cQY2bDDgBgrmtOFC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cQY2bDDgBgrmtOFC .icon-shape p,#mermaid-svg-cQY2bDDgBgrmtOFC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cQY2bDDgBgrmtOFC .icon-shape .label rect,#mermaid-svg-cQY2bDDgBgrmtOFC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cQY2bDDgBgrmtOFC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cQY2bDDgBgrmtOFC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cQY2bDDgBgrmtOFC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端
CREATE STREAM ...
mnode
生成 DAG / 分发任务
snode
执行流任务
触发表
(vnode)
数据源表
(vnode)
输出表
(vnode)
NOTIFY
WebSocket 通知
三条铁律(不理解这三条,一定会踩坑):
- 必须先有 snode,才能建流。 snode 是专门跑流任务的节点,集群里至少要有 1 个;不部署 snode 时流根本无法执行。
- 触发与计算是分离的。 触发来源叫"触发表",计算的数据来源叫"数据源表",两者可以不是同一张表。如果不同,必须确保触发发生时数据源表的数据已经写入完成,否则结果会错。
- 输出表由主键(时间戳 + 分组键)决定唯一性。 同分组同时间戳会互相覆盖;主键为
NULL的结果会被直接丢弃。
2.2 根因归类
| 类别 | 说明 | 对应症状 |
|---|---|---|
| ① 没部署 snode | 集群里流计算节点缺失 | A10 A13 |
| ② 语法/版本不匹配 | 用了旧版 TRIGGER 或过时写法 |
A12 E1 E2 |
| ③ 分组理解错误 | 超级表场景漏 PARTITION BY tbname,导致所有子表结果被聚合到一组 |
B1 B5 |
| ④ 触发方式选错 | 定时 / 窗口 / 计数 / 事件窗口用错场景 | B3 B8 B9 A13 |
| ⑤ 输出表设计错误 | 输出表名规则、主键、TAG 设计不当 | A7 B2 B5 B6 |
| ⑥ 乱序与重算 | 写入乱序导致频繁重算,结果反复覆盖 | B2 B4 C1 C3 |
| ⑦ 历史数据未处理 | 建流前的存量数据没算 | B7 B8 |
| ⑧ 资源抢占 | snode 与 taosd 混部,流负载打满 CPU/IO | C1 C2 |
| ⑨ 依赖对象被误删 | 直接删库/snode 而流还在引用 | D1 D2 D3 D4 |
三、先搞懂流计算:触发方式、分组、输出表
流计算的结果错误,绝大多数不是 bug,而是触发方式 / 分组 / 输出表这三件事没想清楚。
3.1 第一步:部署 snode(建流的前置条件)
sql
-- 在指定 dnode 上创建 snode(每个 dnode 最多 1 个)
CREATE SNODE ON DNODE <dnode_id>;
-- 查看
SHOW SNODES;
SELECT * FROM information_schema.ins_snodes;
部署建议:
- 把 snode 部署在独立 dnode(该节点不再部署 vnode / mnode / qnode),做资源隔离,避免流任务拖慢读写。
- 部署多个 snode(分布在多个物理节点)以获得负载均衡与高可用;每两个 snode 互为副本。
- 只部署 1 个 snode 时,系统不提供高可用保证。
- 流负载高时,增加 snode 即可横向扩展。
DROP SNODE时必须保证 snode 及其副本同时在线,否则会失败(D4)。
3.2 第二步:选对触发方式
| 触发类型 | 语法 | 适用场景 |
|---|---|---|
| 定时触发 | PERIOD(period_time[, offset_time]) |
按处理时间定时运算,可无触发表 |
| 滑动触发 | SLIDING(...) |
根据事件时间定时运算 |
| 时间窗口触发 | INTERVAL(...) SLIDING(...) |
固定窗口聚合,最常用 |
| 计数窗口触发 | COUNT_WINDOW(count[, sliding][, col]) |
每写入 N 条数据处理一次 |
| 事件窗口触发 | EVENT_WINDOW(START WITH ... END WITH ...) |
满足条件开窗、满足条件关窗 |
| 会话 / 状态窗口 | SESSION(...) / STATE_WINDOW(...) |
按业务边界(如订单切换)划分 |
硬性约束(最容易踩):
- 状态窗口、事件窗口、计数窗口搭配超级表时,必须与
PARTITION BY tbname一起使用。 - 时间窗口触发必须指定触发表。
- 定时触发可以没有触发表;但若要分组输出或使用
%%trows,则必须指定触发表。
3.3 第三步:想清楚分组
| 业务目标 | 分组方式 |
|---|---|
| 全局聚合(所有子表合成一个结果) | 不分组 |
| 按标签聚合(如按区域、设备类型) | PARTITION BY <tag_name> |
| 每个子表各自一个结果(最常见) | PARTITION BY tbname |
PARTITION BY tbname是超级表场景的默认答案。 社区里"各子表聚合结果完全一致"(B1)100% 是漏了这一句------不加分组时,所有子表的数据被当作一组聚合,结果当然一样。
3.4 第四步:设计输出表
sql
-- 每个分组独立输出子表(最直观)
CREATE STREAM sm1 INTERVAL(5m) SLIDING(5m)
FROM stb1 PARTITION BY tbname
INTO stb2 AS
SELECT _twstart, avg(col1) FROM %%tbname
WHERE _c0 >= _twstart AND _c0 <= _twend;
关键点:
- 输出表名超长/算不出来 → 检查
OUTPUT_SUBTABLE(tbname_expr)规则与是否有NULL(A7)。 - 多个分组合并到一个子表 → 让这些分组使用相同的输出表名,并设计好复合主键,否则会互相覆盖(B5)。
- 结果主键为
NULL会被丢弃(B6)------确保时间戳列与分组键不为空。 - 选择
%%trows还是FROM %%tbname:
| 写法 | 含义 |
|---|---|
FROM %%trows |
只使用触发时读取到的窗口数据(快照) |
FROM %%tbname WHERE _c0 >= _twstart AND _c0 <= _twend |
查询该分组对应表在窗口内的数据,计算时数据可能已变化 |
3.5 第五步:处理乱序与历史数据
| 需求 | 选项 |
|---|---|
| 容忍一定乱序 | WATERMARK(对 PERIOD 定时触发不生效) |
| 忽略乱序数据(时效性优先) | STREAM_OPTIONS(IGNORE_DISORDER) |
| 丢弃过期数据 | STREAM_OPTIONS(EXPIRED_TIME(exp_time)) |
| 需要计算建流前的历史数据 | STREAM_OPTIONS(FILL_HISTORY) |
| 优先计算历史数据(计数窗口场景必须) | STREAM_OPTIONS(FILL_HISTORY_FIRST) |
| 删除数据也要重算 | STREAM_OPTIONS(DELETE_RECALC)(仅 COUNT_WINDOW(1) / COUNT_WINDOW(n,1) 支持) |
| 极高实时性要求 | STREAM_OPTIONS(LOW_LATENCY_CALC)(对资源要求更高) |
四、标准排查流程(照着做)
第 1 步:确认版本与语法
sql
-- 服务端版本
SELECT server_version();
v3.0.0.0~v3.3.7.0:可用TRIGGER关键字。- 更高版本:必须使用新语法 (
INTERVAL/PERIOD/COUNT_WINDOW/EVENT_WINDOW/SESSION/STATE_WINDOW)。 - 不要照抄旧博客的建流语句,以当前版本文档为准。
第 2 步:确认 snode 已部署
sql
SHOW SNODES;
SELECT * FROM information_schema.ins_snodes;
- 没有任何 snode → 先部署,再建流 (
CREATE SNODE ON DNODE <id>;)。 - snode 离线 → 检查对应 dnode 服务与网络。
第 3 步:查看流任务状态
sql
-- 流列表与状态
SELECT * FROM information_schema.ins_streams;
-- 重算请求跟踪
SELECT * FROM information_schema.ins_stream_recalculates;
重点关注:
- 是否有错误信息;
- 实时计算是否持续保持进度一致(进度落后说明处理不过来);
- 重算次数 / 比例是否异常偏高(说明乱序严重)。
sql
-- 确认创建语句
SHOW CREATE STREAM [db_name.]stream_name;
第 4 步:检查触发与分组是否正确
对照第三节的四个问题自查:
- 触发方式选对了吗?(定时 / 窗口 / 计数 / 事件)
- 超级表 + 状态/事件/计数窗口时,加
PARTITION BY tbname了吗? - 触发表与数据源表是同一张吗?不同的话,触发时数据源表数据写完了吗?
- 输出表名、主键、TAG 设计会导致覆盖吗?
结果"每张子表都一样" → 99% 是第 2 条。
第 5 步:检查是否有乱序写入
TDengine 的乱序定义 :从时间戳 0 起按 DURATION 参数(默认 10 天)划分时间窗口,同一窗口内写入的时间戳未按顺序写入即算乱序。窗口之间交错不算。
sql
-- 查看库的 DURATION 设置
SHOW CREATE DATABASE <db_name>;
处理方式(按业务容忍度选择):
- 大概率可容忍 → 指定
WATERMARK; - 时效性优先、不影响结果 →
IGNORE_DISORDER; - 过期数据且结果已无意义 →
EXPIRED_TIME; - 写入侧尽量保证同一子表的数据由同一消费者顺序写入(Kafka 场景尤其重要)。
第 6 步:检查日志狂刷与资源占用
bash
# 看 snode 与 taosd 的日志
tail -300 /var/log/taos/taosdlog
# 看 CPU / IO
top -Hp $(pgrep taosd)
# 集群 vgroup 状态
taos -s "show vgroups;"
若确认是频繁重算导致:
sql
-- 关闭自动重算(若业务能接受最终一致性风险)
-- 通过流选项控制;也可考虑降低流的并发/线程配置
终极手段:把 snode 迁到独立 dnode,做资源隔离。
第 7 步:需要补算历史数据时
sql
-- 手动重算(SQL 返回成功只代表请求被接受,不代表已完成)
RECALCULATE STREAM [db_name.]stream_name FROM start_time [TO end_time];
注意事项:
- 不适用于
PERIOD定时触发; - 计数窗口手动重算必须同时指定开始与结束时间,否则请求会被忽略;
- 重算不会删除输出表中已产生的结果,可能与新结果同时存在,必要时先清理结果表区间;
- 用
recalc_id与information_schema.ins_stream_recalculates跟踪进度; - 处于
Pending/Running时不要重复提交相同请求。
五、典型场景实操
场景 1:建流报语法错误(旧语法)
现象 :照抄旧文档,CREATE STREAM ... TRIGGER ... 报语法错误。
原因 :TRIGGER 仅适用于 v3.0.0.0--v3.3.7.0。
改造对照:
sql
-- 旧写法(仅 3.3.7.0 及以前)
CREATE STREAM s1 TRIGGER SLIDING(5m) FROM stb1 ...
-- 新写法
CREATE STREAM s1 INTERVAL(5m) SLIDING(5m) FROM stb1 ...
sql
-- 旧:定时
CREATE STREAM s2 TRIGGER PERIOD(1h) ...
-- 新:定时
CREATE STREAM s2 PERIOD(1h) ...
场景 2:各子表聚合结果完全一致
现象 :超级表下每个子表的 avg/min/max 结果一模一样。
原因 :漏了 PARTITION BY tbname。
正确写法:
sql
CREATE STREAM IF NOT EXISTS nbq_hourly
INTERVAL(1h) SLIDING(1h)
FROM product_nbq
PARTITION BY tbname -- ← 关键
INTO product_nbq_hourly
AS
SELECT
_wstart AS ts,
tbname AS device_id,
CAST(AVG(current_value) AS DECIMAL(16,4)) AS avg_value,
MAX(current_value) AS max_value
FROM %%tbname
WHERE _c0 >= _twstart AND _c0 <= _twend;
场景 3:流建好了但完全不计算
排查顺序:
sql
-- ① snode 有没有
SHOW SNODES;
-- ② 流在不在、状态如何
SELECT * FROM information_schema.ins_streams;
-- ③ 触发条件是否被满足
-- 计数窗口:写入条数够不够
-- 事件窗口:START 条件是否出现过
-- 时间窗口:窗口是否已经关闭(未关闭不触发,除非指定 MAX_DELAY)
sql
-- 新建表后需要数据写入才会触发,可先造数据验证
INSERT INTO tb1 VALUES (NOW, 1.0);
场景 4:日志狂刷、CPU 打满、业务变慢
原因:乱序写入 → 频繁重算 → 计算与日志双重压力。
操作:
sql
-- ① 看流的重算情况
SELECT * FROM information_schema.ins_streams;
SELECT * FROM information_schema.ins_stream_recalculates;
-- ② 评估乱序容忍度,重新建流并加上合适的选项
CREATE STREAM sm_fixed
INTERVAL(5m) SLIDING(5m)
FROM stb1 PARTITION BY tbname
STREAM_OPTIONS(IGNORE_DISORDER | MAX_DELAY(1m))
INTO stb2 AS
SELECT _twstart, avg(col1) FROM %%tbname
WHERE _c0 >= _twstart AND _c0 <= _twend;
bash
# ③ 资源隔离:把 snode 部署到独立 dnode
# 在该 dnode 上不部署 vnode/mnode/qnode
场景 5:结果重复写入
现象:输出表里同一批数据出现多条。
原因(按概率)
- 同一个分组多次触发产生相同主键 → 正常应互相覆盖;若表没有主键约束或时间戳计算有误,就会重复。
- 多个分组输出到同一个子表但主键设计不当。
- 手动重算后没有清理结果表。
操作:
sql
-- 检查输出表主键设计
DESCRIBE <output_table>;
-- 重算前先清理该区间结果
DELETE FROM <output_table> WHERE ts >= '<start>' AND ts < '<end>';
场景 6:会话/订单类业务边界被跨越
现象 :用 COUNT_WINDOW(2,1) 计算相邻两条差值时,把上一订单的最后一条和下一订单的第一条配成了一对。
正确做法 :外层用 STATE_WINDOW 定义业务边界,内层再计算:
sql
CREATE STREAM order_pair
STATE_WINDOW(order_id) AS -- 外层:业务边界
WINDOW(COUNT_WINDOW(2, 1) AS pair) -- 内层:计算粒度
FROM charging_pile
STREAM_OPTIONS(EVENT_TYPE(WINDOW_CLOSE))
INTO order_pair_results
AS
SELECT _twstart, soc_delta FROM pair;
注意 :非叶 STATE_WINDOW 必须指定 EXTEND(1);触发表为超级表且窗口链包含 STATE_WINDOW / EVENT_WINDOW / COUNT_WINDOW 时,必须 PARTITION BY tbname。
场景 7:计算建流前的历史数据
sql
-- 优先算历史(计数窗口场景建议)
CREATE STREAM sm_hist
INTERVAL(5m) SLIDING(5m)
FROM stb1 PARTITION BY tbname
STREAM_OPTIONS(FILL_HISTORY_FIRST)
INTO stb2 AS
SELECT _twstart, avg(col1) FROM %%tbname
WHERE _c0 >= _twstart AND _c0 <= _twend;
FILL_HISTORY:也计算历史数据;FILL_HISTORY_FIRST:优先计算历史数据;- 计数窗口若不优先算历史,可能导致窗口无法衔接。
场景 8:删除 snode / 数据库失败
sql
-- 删 snode 前先确认没有流在用它
SHOW SNODES;
SELECT * FROM information_schema.ins_streams;
-- 报 Snode still in use with streams → 先删相关流
DROP STREAM <stream_name>;
-- 报 Db used by stream → 先删引用该库的流
-- 确认 snode 及其副本都在线后再删
DROP SNODE ON DNODE <dnode_id>;
六、错误码速查
| 错误码 | 报错文本 | 含义 | 处理方向 |
|---|---|---|---|
0x80002645 |
Invalid stream query |
非法流语句 | 修正 SQL |
0x8000265A |
Stream not allowed |
函数不能在流中使用 | 换函数 |
0x800003F0 |
Stream already exists |
流已存在 | 改名字 |
0x800003F1 |
Stream not exist |
流不存在 | 确认库名/流名 |
0x800003F3 |
Stream must be dropped first |
有依赖不能删 | 先删流 |
0x800003F5 |
Stream ... source db having replica > 1 |
源库副本数受限 | 调整副本数 |
0x800003F6 |
Too many streams |
流数量超限 | 精简流 |
0x800003F9 |
Stream has no stored CREATE statement |
低版本建的流 | 重建流 |
0x8000040F / 0x80000410 / 0x80000411 |
Snode already deployed / not found / not deployed |
snode 状态问题 | 部署/检查 snode |
0x80000411 |
Snode not deployed |
未部署 snode | CREATE SNODE ON DNODE <id> |
0x80000807 |
Stream creation limited by license |
流数量超授权 | 联系交付 |
0x80003154 / 0x80003155 |
Rsma stream state open / commit |
流算子状态存储失败 | 查日志,联系开发 |
0x80004100 |
Stream task not exist |
流任务不存在 | 查服务端日志 |
0x80004118 |
Stream rollup tag path is illegal |
ROLLUP BY 路径非法 |
修正路径 |
0x8000411A |
Federated query is disabled for stream |
外部源引用未开联邦查询 | 开启 federatedQueryEnable 或移除引用 |
0x80007007 |
Snode still in use with streams |
snode 被流占用 | 先删流 |
0x8000700E |
Db used by stream |
库被流引用 | 先删流 |
0x80007014 / 0x80007016 |
Stream output table name too long / calc failed |
输出表名问题 | 检查命名规则与 NULL |
0x80007017 |
Stream vtable calculate need redeploy |
虚拟表分布变更 | 流会自动处理,无需干预 |
0x80007018 |
Stream info contains invalid JSON |
内部编码兼容问题 | 保留现场,上报 issue |
七、预防清单
- 建流前先部署 snode ,并尽量部署在独立 dnode。
- 集群部署多个 snode 做负载均衡与高可用(单 snode 无高可用保证)。
- 以当前版本文档为准写建流 SQL ,不要照抄旧博客的
TRIGGER写法。 - 超级表场景默认加
PARTITION BY tbname。 - 明确"触发表"与"数据源表"的关系,确保触发时数据已写完。
- 输出表:设计好输出表名规则 + 主键 + TAG,避免覆盖与丢弃。
- 写入侧尽量保证顺序(Kafka 一个子表一个消费者),从源头减少重算。
- 按业务容忍度设置
WATERMARK/IGNORE_DISORDER/EXPIRED_TIME。 - 需要历史数据时显式指定
FILL_HISTORY/FILL_HISTORY_FIRST。 - 定期查
ins_streams:进度一致性、重算比例、错误信息。 - 流负载高时通过增加 snode 扩容,而不是硬扛。
- 删库/删 snode 前先确认没有流在引用。
八、求助模板(贴在社区里,回复会快很多)
text
【TDengine 使用环境】生产 / 预生产 / 测试 / PoC
【TDengine 版本】服务端:x.x.x.x 客户端:x.x.x.x
【操作系统及版本】___
【部署方式】容器 / 非容器
【集群节点数】___ 【副本数】___ 【snode 数量】___
【问题类型】建流失败 / 结果不符预期 / 日志狂刷 / 维护操作失败
【完整建流语句】(把 CREATE STREAM 全文贴出来,这是最关键的信息)
【现象描述】期望结果 vs 实际结果
【已排查】
- SHOW SNODES:___
- SELECT * FROM information_schema.ins_streams:___
- SHOW CREATE STREAM <name>:___
- 触发表写入是否有序:___
- 服务端版本:___
【日志】/var/log/taos/taosdlog 关键片段 + /etc/taos 配置
九、相关链接
- 社区问答:https://ask.taosdata.com
- 提交 Issue:https://github.com/taosdata/TDengine/issues
- 官方文档:https://docs.taosdata.com
- 典型讨论帖:
本文整理自 TDengine 技术社区真实提问,覆盖 3.0 至 3.4 各版本的流式计算相关问题。如果你遇到的情况不在上述症状列表中,欢迎到社区发帖并附上本文第八节的求助模板。