一条产线的设备健康看板,采集侧每台机组每秒写一批测点,分析侧要在 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",这是厂商口径,建立在列式压缩之上,没有独立测试,做预算时按自己的数据分布压一遍再乘。