导语: 工业物联网项目里有个常见的滞后效应:字段怎么设计、宽表还是窄表、选哪个存储引擎、分区列怎么定,这些建模阶段的选择往往是项目初期图省事定下来的——设备少、数据量小,怎么定都跑得动,问题看不出来。等到设备越接越多、数据量越滚越大,查询变慢、写入卡顿、想改分区又发现数据已经写了大半年改不动了,当初的选择才开始显出代价,而这时候要付出的往往是数倍的返工成本。
本文以一个实际场景为例,结合 DolphinDB 带大家数据建模阶段的决策逻辑捋一遍,再看看数据该如何顺利接入进来。
以一个典型场景为例
某工厂车间里部署了一万个传感器,每 30 秒上报一次温度、压力、湿度、电压、电流和状态信息。按这个频率算下来,单日数据量接近三千万条。
对于这种典型的工业监控场景来说,数据设计的重点不在于“怎么把数据塞进去”,而在于如何把数据组织好,让它既能持续写入,也能高效查询,并且方便后续扩展。
第一步:数据建模
建模的第一步不是急着定分区,在这之前需要先想清楚三件事:字段怎么设计,指标应该按宽表存储还是窄表存储,以及查询场景决定了该用哪个存储引擎。
1. 字段设计
字段设计阶段用于确定数据表里需要记录哪些字段、每个字段用什么类型存储——既要覆盖设备上报的全部信息,也要避免类型选得过宽造成存储浪费。
上述场景的字段设计如下:
2. 宽表还是窄表
本场景中温度、压力、湿度等指标由同一设备在同一时间点统一上报,且指标集合相对固定,因此合并成一张宽表,便于按设备、按时间做多指标联合查询,也能减少关联查询开销。如果指标上报频率不一致、字段经常变动,则更适合拆成窄表。
3. 存储引擎
DolphinDB 的两种主要存储引擎里,OLAP 引擎擅长大批量、跨设备的聚合分析(比如全厂设备的月度能耗统计),TSDB 引擎则针对“按设备+时间”的高并发点查和最新值查询做了专门优化——通过排序列和分区裁剪加速这类查询。本例最常见的查询场景正是按单个设备、按时间范围调取数据,因此存储引擎选 TSDB。
4. 分区方案
分区是 DolphinDB 组织海量数据的核心方式——查询命中分区列时只扫描相关分区,能大幅减少 I/O;分区太大或太小,都会拖慢查询甚至压垮节点内存。
在 DolphinDB 中,分区方案不是拍脑袋定的,而是取决于两点:
-
常用查询条件。比如本例最典型的是“按设备+时间”查询,这决定了 device_id 和 ts 是首选的分区列
-
存储引擎推荐的单分区大小,和分区粒度挂钩,是内部实践给出的一些经验性指标
根据字段设计对数据量进行估算:
-
单行大小 ≈ 4(device_id) + 8(ts) + 8×5(5个DOUBLE指标) + 4(status) = 56字节
-
每天总量 ≈ 28,800,000行 × 56字节 ≈ 1.5 GB
TSDB 推荐的单分区粒度为 400MB~1GB,如果只按天分区,每天 1.5GB 会偏大。
所以更合理的方案是:日期 VALUE 分区 + 设备 ID HASH 分区。 这样可以把单分区数据量压下来,也更适合后续查询和写入。另外,HASH 分区数一旦建库后就很难再改,建议结合未来 1~3 年的设备增长预期,一次性规划到 4~5 个。
第二步:建库建表
// 步骤1:创建复合分区数据库
create database "dfs://iot_sensor"
partitioned byVALUE(2025.01.01..2025.01.03), HASH([SYMBOL, 5])
engine='TSDB'
// 步骤2:创建分区表
create table "dfs://iot_sensor"."sensor_data" (
device_id SYMBOL[comment="设备id"],
ts TIMESTAMP[comment="时间", compress="delta"],
temperature DOUBLE[comment="温度"],
pressure DOUBLE[comment="压力"],
humidity DOUBLE[comment="湿度"],
voltage DOUBLE[comment="电压"],
currentDOUBLE[comment="电流"],
status INT
)
partitioned by ts, device_id
sortColumns=["device_id","ts"]
这里有 3 个关键点:
- 引擎选 TSDB: 更适合“按设备 + 时间”查询,以及窗口聚合场景。
- 分区列同时包含ts和 device_id:和前面的复合分区方案一一对应。
- 排序列设为 device_id, ts:排序列用于决定数据写入时的排序和查询索引,且要求最后一列必须是时间列。这样设置按设备查询时,更容易命中排序索引。
TSDB 索引机制示意图
第三步:数据接入
工业现场的数据入口通常不止一个,写入方式要按场景来选。
(1)API 批量写入
适合网关程序、边缘服务等周期性攒批上传的场景。
DolphinDB 提供了Python、Java、C++、Go 等多语言接口,方便应用侧完成数据写入、查询和计算。
以 Python API 为例:
import dolphindb as ddb
import pandas as pd
# 1. 连接 DolphinDB
s = ddb.session()
s.connect("127.0.0.1", 8848, "admin", "123456")
# 2. 构造一批模拟数据
data = pd.DataFrame({...})
# 3. 上传 DataFrame 到 DolphinDB 会话
s.upload({"data": data})
# 4. 批量写入分布式表
s.run("""
pt = loadTable("dfs://iot_sensor", "sensor_data");
tableInsert(pt, data)
""")
print("write success")
为了适配不同数据规模和实时性要求,DolphinDB API通常提供了批量写入、分区并发写入、流式异步写入等多种写入方式:
简单来说,可以概括为:
-
小批量场景:
tableInsert、tableAppender就够用 -
高并发 / 高吞吐实时场景:建议用
MultithreadedTableWriter或PartitionedTableAppender(自动按分区键路由写入)
需要注意的是,受 Python 本身 GIL 锁限制,多线程的 PartitionedTableAppender 写入性能有时反而不如单线程 TableAppender,实际选型建议结合数据量做测试对比。
(2)消息中间件订阅
适合 Kafka、MQTT 等实时链路,能把设备侧和分析平台解耦,同时起到削峰填谷的作用。
DolphinDB 提供了 Kafka、MQTT、RabbitMQ、RocketMQ 等消息队列插件,可以在插件市场(marketplace.dolphindb.cn)查看和下载,也可以通过内置的 listRemotePlugins 和 installPlugin 函数远程获取和安装安装,无需单独编译。
下载完成后只需要几行脚本,即可对接消息队列的数据到 DolphinDB。以 Kafka 为例:
loadPlugin("kafka")
consumer = kafka::consumer(...)
kafka::subscribe(...)
kafka::createSubJob(...)
(3)历史数据迁移
适合从 MySQL、Oracle 等关系型数据库迁移。
DolphinDB 提供了一系列插件支持跨数据库间的迁移:
-
通过 MySQL、MongoDB、Redis、HBase 等专用插件对接
-
对于没有专用插件、但提供 ODBC 驱动的数据库,通过 ODBC 插件统一对接
-
通过基于 DataX 的 dolphindbwriter 插件,配合 DataX 生态里现成的 Reader 插件,实现更多数据源的离线同步
本文以 ODBC 插件为例:
loadPlugin("odbc")
conn = odbc::connect("DSN=OracleDB;UID=iot_user;PWD=iot_password")
t = odbc::query(conn, "select device_id, ts, temperature, pressure, humidity, voltage, current, status from sensor_history")
pt = loadTable("dfs://iot_sensor", "sensor_data")
tableInsert(pt, t)
(4)文件批量导入
对于 TXT、CSV 等文本文件,可以通过内置的 loadText、loadTextEx 函数导入;对于特殊文件格式,DolphinDB 也提供了对应插件:
-
Parquet 插件:支持列式存储文件的高效读取和转换
-
Arrow 插件:便于与其他系统进行数据传输并自动完成类型转换
-
HDF5 插件:适合导入科研设备、大型实验装置产生的高频数据文件
另外如果历史数据是压缩包形式,还可以用 zip 插件进行解压。
本文以 loadText 为例:
loadText 用法简单,导入后生成内存表,便于预览数据、检查字段类型和简单清洗,适合小规模数据验证阶段。
// 将小型 CSV 文件加载到内存表
data = loadText("/path/to/small_data.csv")
// 数据清洗
clean_data = select * from data where time between 09:00 and 16:00
// 将内存表追加到分布式表
pt = loadTable("dfs://iot_sensor", "sensor_data")
tableInsert(pt, clean_data)
小结
工业物联网数据建模,核心不是先写代码,而是先把三件事想清楚:数据从哪里来、数据量有多大、典型查询是什么。
相比传统关系型数据库,DolphinDB 更适合应对工业场景里高频写入、时序查询和大规模分析的需求;同时它把存储、计算和接入能力整合在一起,也能减少后续系统拼接和二次开发的成本。
在“设备 + 时间”这类场景下,DolphinDB 的 TSDB 引擎配合日期 VALUE 分区、设备 ID HASH 分区和宽表模型,通常就能兼顾写入、查询和扩展。数据接入层面,它也提供了比较完整的方案:小批量场景可以用 API 或文件导入;实时链路可以接 Kafka、MQTT 等消息中间件;历史数据迁移则可以通过数据库插件完成。
把建模和接入这两步先理顺后,后面设备再多、数据再大,系统也更不容易被迫重来。