一条产线的设备健康看板,采集侧每台机组每秒写一批测点,分析侧要在 BI 里直接写 SQL,还要 JOIN 一张设备维表拿到型号和产线。这两件事对数据库的要求是冲突的:采集侧要的是窄表、高基数、按设备前缀批量写;分析侧要的是标准 SQL、能关联、能开窗。
IoTDB 2.0 想同时接住这两头:保留原来的树模型,加一个表模型,两套 SQL 语法跑在同一个引擎上。这篇不谈它有多快,只谈两套模型各自的能力范围和适用条件。
树模型与表模型
树模型的路径是 root.group.device.sensor,一条路径定位到一个测点。设备层级天生清晰的场景,这个模型几乎没有学习成本,老用户也不需要动。
表模型换了一套说法:database 管多类设备,一类设备一张时序表,列分成四类——TIME、TAG、FIELD、ATTRIBUTE,主键是所有 TAG 列加 TIME 列。这是关系库用户熟悉的样子,BI 工具和 SQL 客户端接进来不需要额外适配。
两套模型不是二选一的部署选项。同一个集群实例可以同时用,语法彼此隔离,这一点对存量迁移很关键:不用一次性把老查询全改掉。
表模型的列与主键
四类列决定了后面所有查询的效率。
TIME 是时间戳,TAG 是设备标识这类用来定位的维度,FIELD 是真正的测点值,ATTRIBUTE 是设备的静态属性(型号、产线、安装日期)。主键由所有 TAG 列加 TIME 组成,同一台设备同一时刻只有一行。
建表和写入跟关系库接近:
-- 一类设备一张表:TAG 定位设备,FIELD 存测点,ATTRIBUTE 存静态属性
CREATE DATABASE plant_ops;
CREATE TABLE plant_ops.turbine_reading (
time TIMESTAMP,
site STRING TAG,
turbine_id STRING TAG,
model STRING ATTRIBUTE,
power DOUBLE FIELD,
vibration DOUBLE FIELD
);
表模型支持写入时自动建表、自动扩列,TAG、ATTRIBUTE、FIELD 三类都能动态加,不需要先跑 DDL。新增一个测点,第一次写入就带出来:
-- 新测点不预先建列,第一次写入时自动出现
INSERT INTO plant_ops.turbine_reading
(time, site, turbine_id, model, power, vibration, bearing_temp)
VALUES ('2026-10-28T08:00:00', 'shenzhen', 'T-014', 'X2', 812.4, 3.17, 61.2);
一台设备的测点集合经常在变,加一个传感器就多一列。测点列数量没有上限,可以支持到数十万级;传统关系库常见的 1024 列上限在这里不成立。表结构一年都不变的场景,这个优势用不上。
TsFile 那一层
两套模型能互转,根本原因在存储层。两种模型共享同一份 TsFile,而 TsFile 只认"设备"和"测点"两个概念,树和表都只是用户侧的视图。模型切换不会带来数据搬迁。
树模型转表模型用 CREATE VIEW 加 AS 建逻辑视图:
-- 老树模型的子树直接当表查,底层文件不动
CREATE VIEW plant_ops.line_a AS root.plant_a;
只有测点 ID、没有设备概念的场景也能映射过去。老系统里按测点编号组织的路径,不用先补一层设备模型就能用表模型。
索引的差别也得说清。TIME 和 TAG 列有稀疏索引,过滤效率最高;ATTRIBUTE 次之;FIELD 列只有文件内 block 级稀疏索引,过滤条件最严格,扫的量最大。所以表设计的第一原则是把常用于过滤的维度放进 TAG。把产线号写成 FIELD 再按它过滤,等于放弃索引。
SQL 能力的实际差别
表模型支持的标准 SQL 覆盖面比树模型宽:四种 JOIN(INNER、LEFT、RIGHT、FULL OUTER)、子查询、count(*)、count(distinct)、窗口函数、多态表函数,以及 SQL:2016 的行模式识别。
有一个语义差异容易被忽略:树模型的 count(*) 是对匹配到的每个测点列分别统计非空值,返回多列;表模型的 count(*) 才是标准语义的行数。同名函数两种语义,做报表核对时会对不上。
结构性的限制在 FROM 子句:树模型的 FROM 是前缀路径模式,无法嵌套子查询;表模型可以。重叠时间分窗也是两套写法,树模型用自定义语法,表模型用 HOP 表函数,命名和 Flink 一致。
表模型还有一个树模型没有的能力:ASOF JOIN。它来自 Pandas,容忍时间戳的微小偏差,按最近的时间戳对齐。两个测点采集起点差几毫秒,用等值 JOIN 会对不齐、丢数据,ASOF JOIN 正是为这个场景准备的。
CTE 的递归 WITH 子句尚未支持,在迭代计划里;需要递归展开层级的查询得在应用层拆。
性能上,官方口径是简单查询、尤其短查询,树模型目前仍优于表模型;表模型的优势在分析函数和标准 SQL 兼容。这句话反过来读就是:单点取最新值的场景,换成表模型不会更快。
横向对比
下面这张表只比能力维度,不含任何性能数字。各家具体版本的行为以官方文档为准。
| 维度 | IoTDB 2.0 | TDengine | TimescaleDB | InfluxDB | ClickHouse | 传统关系库 |
|---|---|---|---|---|---|---|
| 数据模型 | 树模型 + 表模型,共享 TsFile | 超级表 + 子表,一设备一子表 | PostgreSQL 扩展,按时间分片 | measurement + tag/field | 宽表,无设备概念 | 单表宽列 |
| SQL 兼容 | 表模型走标准 SQL,树模型前缀路径 | 类 SQL 方言 | 完整 PostgreSQL SQL | InfluxQL / Flux | SQL 较强 | 完整 |
| JOIN | 四种 JOIN + ASOF JOIN | 有限,以文档为准 | 完整 | 弱 | 支持,大表关联需设计 | 完整 |
| 分析函数 | 窗口、多态表函数、行模式识别 | 部分支持 | 随 PostgreSQL | 有限 | 强 | 视版本 |
| 边缘部署 | 端边云协同 | 有边缘版 | 需自带 PG 运维 | 有轻量部署形态 | 偏重 | 视产品 |
| 生态连接器 | Spark、Flink(表模型 Table API 未完成) | 较多 | 随 PG 生态 | 偏监控生态 | 较广 | 通用 |
| 宽表列数 | 测点列数十万级 | 视建模方式 | 受 PG 限制 | 无固定列概念 | 宽表友好 | 常见 1024 列上限 |
先划掉不满足硬条件的行,再在剩下的里选。
选型判断
设备层级天然清晰、以短查询为主(取最新值、按设备前缀批量读),选树模型。IoTDB 老用户不用动,这也是官方保留树模型的理由。
需要 JOIN 业务维表、BI 或分析师直接写 SQL、有复杂窗口和事件模式识别,选表模型。判断标准很简单:把一条真实报表 SQL 拿去试,如果需要函数嵌套、子查询、维表关联,树模型那条路走不通。
两个模型可以按模块分开用,因为同一实例能并存、语法隔离。但这不是免费的:两套 DDL 习惯、两套查询写法,模块边界不清楚的话,最后会混在一个报表里。
已经在用 Flink 做实时链路的,这笔账要单独算。现有 Flink 连接器主要面向树模型,2.X 表模型的原生 Table API 支持还是 Apache 社区的 GSoC 项目,尚未完成。也就是说选了表模型,实时链路要么继续读写树模型视图,要么等连接器。
不适合的场景
数据量只有几十万行的,直接用关系库。IoTDB 的收益来自按设备组织的海量测点和压缩,几十万行的规模连索引都用不上,多引入一个组件只是多了运维负担。
团队没有运维时序库经验的,要谨慎。坑集中在保留策略、乱序写入、compaction 上,和关系库不是一套知识。
需要强事务 OLTP 的,不要选。时序库定位是写多读少的分析型负载,订单、账务不在能力范围内。设备台账放关系库、测点放 IoTDB,应用层关联即可。
成本上,官方口径是"硬盘存 1GB 数据成本低于 $0.23",这是厂商口径,建立在列式压缩之上,没有独立测试,做预算时按自己的数据分布压一遍再乘。