煤机设备的数据规模,很多人是没有具体概念的。一台采煤机身上几百上千个测点,温度、压力、振动、电流、油位这些传感器按秒甚至毫秒级回传,矿上几十台设备跑一天,集团所有矿井的数据汇总起来,一晚上落几个 TB 是常有的事。天地奔牛这种煤机装备制造与运维的一线企业,面对的就是这种每天都在涨的时序数据,而他们最终选择了 TDengine,硬生生把分钟级都出不来的查询压到了秒级。
这篇文章我不打算写成产品宣传稿,而是以一个参与过同类项目建设的技术人视角,把这类“TB 级工业时序数据”场景下的选型逻辑、数据建模、写入链路、查询优化、部署运维这几个关键环节整个拆一遍。用到的方法和思路,放到任何一个做工业物联网、设备状态监测、能耗监控的项目里都能直接参考。如果你现在正被 MySQL 大表慢查询折磨,或者刚刚接触 TDengine 准备迁移,这篇文章应该能让你少走很多弯路。
1. 先搞清楚“每天 TB 级”的压力到底从哪来
做技术选型之前,最怕的就是把“数据量大”挂在嘴边,却说不清楚量大在哪里、卡点是什么。所以我先带你把账算明白,再看传统方案为什么顶不住。
1.1 一个煤机场景的真实数据画像
煤机设备和普通工业设备不一样,它不是一个传感器排一个数据的简单模型。以采煤机为例,牵引系统、截割系统、液压系统、冷却系统都有各自的传感器:电机电流和功率这类电气量,往往是几十赫兹采集;温度、压力、流量这些缓变量,1 秒一条就够了;振动通道则是重头戏,振动加速度信号要做到每秒几千个采样点,一条通道的数据量能顶几百条温度数据。
按一个集团下多矿井来估算:假设有 3000 台在运行设备,平均每台设备挂 400 个低频测点,按 1Hz 采样,每秒就是 120 万条数据。每条数据包括时间戳、设备编码、测点编号、数值、质量码,算 40 字节,一秒钟就是 48MB,一天下来 4.1TB 左右。再加上部分重点设备的振动高频采集,30 台设备每台 32 通道、每通道 2kHz,每秒又增加约 192 万条,一天再多 1.3TB。这个规模合计起来,一天 5TB 级别是完全正常的。
这里要注意,我说的还是“原始数据”。实际生产里还要叠加设备启停记录、报警事件、PLC 脚本日志这些非规则数据。如果做了边缘端处理,可能还要把预处理后的特征值一并入库。所以“TB 级”不是夸张,而是这个行业的基本盘。
1.2 老架构为什么在这个数据量下崩掉
很多煤炭企业最初的数据库方案是 MySQL,因为历史系统都是这么建的。设备数据进来之后,按天建表、按月分库,一开始几千台设备的时候确实能跑,但等到测点一多、保留周期拉长,问题就全暴露出来了。
第一个问题是写入锁竞争。工业数据是持续的高并发写入,InnoDB 的行锁和间隙锁在这种写密集场景下非常吃力,主从延时会从几秒慢慢涨到几分钟,从库查询的数据滞后到完全没有监控价值。第二个问题是查询性能断崖式下跌。一张几亿行的明细表,就算建了索引,做一次跨设备、跨时间段的 AVG、MAX、MIN 聚合,MySQL 要全表扫或者全索引扫,一个查询跑几十秒很常见,BI 报表彻底变成“转圈大赛”。
也有人转向 HBase、ES 这类分布式存储。它们确实能扛写入,但查询建模很痛苦。HBase 要自己管理聚合逻辑,ES 对时序数据的存储和压缩开销偏大,而且都要求你会养一个分布式团队。对一个煤机装备企业来说,使用成本高得离谱。
1.3 为什么选 TDengine:核心是模型匹配
这个项目选择 TDengine,最大的原因在于数据模型天然匹配。TDengine 的核心抽象是超级表、子表、标签,翻译成工业语言就是“一类设备、一台具体设备、设备的静态属性”。煤机设备的数据本质上就是“每个测点按固定周期产生数值”,这正是时序数据库的标准场景。TDengine 把存储、写入、查询、保留策略做成了一体化的东西,不用像传统架构那样拿关系库和缓存拼拼凑凑。
压缩效率也是实打实的优势。工业数据重复性极高,电机电流在一段时间内变化很小,TDengine 列式存储配合压缩算法,在我实测过的项目里压缩比能做到 5:1 到 10:1。上面说的每天 5TB 原始数据,落盘后可能只有 500GB 左右,一年下来 100TB 以内的存储成本完全可控。
至于网上经常有人吐槽“TDengine 太贵了”,这里要说句公道话:社区版是开源免费的,单集群小规模部署完全够用,天地奔牛这种体量,社区版起步没有压力;企业版是商业软件,收费提供的多节点集群规模、高级权限管理、官方支持这些,对比自研存储引擎的人力投入,其实谈不上贵。贵的从来不是软件,是自己折腾的隐性成本。
2. 数据模型设计:从 MySQL 宽表到超级表+子表的关键一跳
选型定了,真正的工程量在设计数据模型。这是决定后续查询性能的基础,也是网上“MySQL 表结构自动转 TDengine 超级表 + 子表”这类问题被反复讨论的原因。
2.1 超级表、子表和标签到底怎么理解
如果你刚接触 TDengine,我建议用最朴素的方式理解:超级表是一个“模板”,它定义了这批数据有哪些列(比如时间戳、电机电流、泵站压力、振动有效值);子表是模板的一个具体实例,每台设备建一张子表,数据落在子表里;标签则是贴在子表上的说明卡片,记录设备编号、所属矿井、设备型号、生产厂家这些不变属性。
为什么把静态属性拆出来当标签而不是当普通列?两个好处。一是过滤效率高,查询时先按标签把子表筛出来,再在子表范围内扫描数据,你查“某个矿所有采煤机最近 5 分钟的电流均值”,数据库只需定位到十几张子表,而不是扫描整张宽表;二是存储冗余小,标签不会跟着每条数据重复存,单条记录只有时间戳和测点值,体量立刻就小了。
与之相关的“多个表时序一致”问题,在 TDengine 里没有玄学。它不是分布式事务数据库,跨子表写入没有多表事务保证,也不需要那种保证。时序一致性靠的是采集端时钟同步、同一设备同一批次的数据一次性写入、以及查询时按 INTERVAL 时间窗口对齐这三板斧。只要采集网关的时间统一走 NTP,并且一个设备的数据由同一个采集进程负责分发,多子表在时间轴上的对齐是完全做得到的。
2.2 建表实操:字段注释和测点分组
给一个我能直接用的建表样例。煤机设备按照“同一采样频率的测点放一张超级表”的原则拆分,低频秒级数据放一张表,高频振动数据单独放一张表,避免一张表里既有 1Hz 列又有 kHz 列导致稀疏浪费。
CREATE STABLE shearer_sec ( ts TIMESTAMP, motor_current FLOAT COMMENT '截割电机电流', motor_temp FLOAT COMMENT '截割电机温度', pump_pressure FLOAT COMMENT '泵站压力', flow_rate FLOAT COMMENT '泵站流量', vibration_rms FLOAT COMMENT '振动有效值' ) TAGS ( device_id NCHAR(32) COMMENT '设备编号', device_type NCHAR(16) COMMENT '设备类型', mine_id NCHAR(16) COMMENT '矿井编号', vendor NCHAR(64) COMMENT '设备厂商' );这条 SQL 里有几个细节值得注意。很多从 MySQL 转过来的人会忽略字段注释,结果几十个测点全靠猜,管维的人换一茬就没人知道哪个列代表什么。TDengine 支持 COMMENT 写列注释和标签注释,这件事在建表之初就做掉,后面能省一万个因为“这列是电流还是电压”而起的沟通成本。
再有就是时间戳类型必须显式指定。TDengine 的主键就是时间戳,它决定了数据分布和索引方式。工业场景统一用毫秒精度就够了,秒级精度太粗,微秒和纳秒又会吃得更多存储,除非采集端确实有这个需求,否则不要盲目上微秒。
2.3 从 MySQL 迁移的步骤与自动转换思路
MySQL 表要迁移到 TDengine,先不要急着导数据,先把表结构理清楚。我在实际项目中按这个顺序操作:
第一步,找出所有同时含有“设备编码 + 时间”字段的表,这些表才有迁移价值,纯配置表继续留在 MySQL。第二步,按采样频率分组,把同频测点合成一张超级表,这里可以通过列名前缀做初步判断(比如vib_开头的归振动表,temp_开头的归温度组)。第三步,生成建表语句,顺序是建超级表、批量建子表、最后导数据。子表用“模板 + 标签”自动创建:
CREATE TABLE shearer_sec_001 USING shearer_sec TAGS ('SH-001', 'shearer', 'M01', '奔牛智造');数据导出的思路也简单:从 MySQL 直接查询生成 CSV,按时间戳排好序,再用 TDengine 的导入工具批量灌入。但这中间有三个容易踩的坑:MySQL 如果存的 DATETIME 有时区,必须先统一转换成 UTC;自增 ID 不要带进 TDengine,时序数据不需要它;质量码 null 那些脏数据,最好在导出阶段就清洗掉,别把“修数”的工作拖到大表里做。
2.4 单测点多行还是多测点宽列:一锤定音的选型
TDengine 常用的模型有两种:一行一条测点的窄表模型(ts, device_id, metric, value),和一列一个测点的宽表模型(ts, motor_current, motor_temp, ...)。这是 MySQL 用户最容易纠结的地方。
我的看法是:工业设备监测数据,只要测点集合相对固定,就选宽表模型。原因有几点。宽表一次写入就是一台设备一个时间点的完整快照,查询时算均值、峰值不用先按 metric 做行转列,SQL 写起来优雅得多;同一时刻的多个测点落在同一行,列压缩率更高,因为相邻列在物理存储上密集排列;子表数量也少,一张子表就是一台设备,管理粒度清晰。
窄表模型也不是一无是处,它适合测点动态增减、不同设备测点差异大的场景。但代价是查询要频繁 group by metric,性能损耗肉眼可见。所以我给出的原则是:能用宽表就别用窄表。
3. 写入链路设计:每天 TB 级的数据怎么稳定落库
模型设计好了,接下来是写入链路。这是整个系统里最容易出幺蛾子的环节,因为工业现场的网络抖动、设备重启、断电,都会直接反映到写入数据上。
3.1 完整的数据接入链路参考
天地奔牛这类项目,数据不会直接裸连数据库,链路通常是:
现场设备 → PLC/传感器 → 边缘采集网关 → MQTT Broker / Kafka → 数据接入服务 → TDengine
边缘采集网关负责把各种协议(Modbus、OPC UA、CANopen)统一转成标准 JSON,再推到消息队列里做削峰。数据接入服务消费消息队列里的数据,批量拼装成 SQL 参数,写入 TDengine。消息队列在这条链路里不是可选项,因为设备上报往往是突发性的,比如换刀、启停瞬间会产生大量数据,没有队列缓冲,数据库会被瞬时流量打穿。
TDengine 3.x 提供了 taosAdapter 这样的标准接入组件,可以直接消费 Kafka 数据,也可以走 REST 接口。实际项目中我建议技术栈这么分:如果接入端是大数据生态(Spark、Flink),走 taosAdapter 消费 Kafka 最顺滑;如果接入端就是自研的采集程序,直接用原生连接器写入更高效。
3.2 C++ API 与批量写入要点
煤机设备边缘采集程序很多是用 C/C++ 写的,因为要直接和 PLC 底层打交道。TDengine 的 C/C++ 连接器在这种场景下非常合适,它走原生 TCP 协议,性能好,还支持参数绑定。
一个典型的 C 代码片段长这样:
#include <taos.h> #include <stdio.h> int main() { taos_init(); TAOS* conn = taos_connect("192.168.1.10", "root", "taosdata", NULL, 6030); if (!conn) { fprintf(stderr, "connect failed: %s\n", taos_errstr(conn)); return -1; } TAOS_STMT* stmt = taos_stmt_init(conn); // 绑定子表名 taos_stmt_set_tbname(stmt, "shearer_sec_001"); // 预编译插入语句 taos_stmt_prepare(stmt, "INSERT INTO ? VALUES (?, ?, ?, ?, ?, ?)", 0); // 绑定时间戳和字段值的参数数组,循环赋值后执行 // taos_stmt_bind_param_batch(stmt, ...); // taos_stmt_execute(stmt); taos_stmt_close(stmt); taos_close(conn); taos_cleanup(); return 0; }这里核心的心法是批量和预编译。预编译语句编译一次、多次执行,避免每条数据都走完整 SQL 解析;批量绑定参数一次写几百行,吞吐量比单条 INSERT 高一个数量级。我在现场调过一条经验:单条 INSERT 写入大概每秒几千条就把应用压垮了,改成批量 500 条一批之后,单线程轻松每秒几十万条,应用 CPU 反而降了。
3.3 写入吞吐与存储容量的估算方法
接需求的时候,最怕销售或者业务方甩一句“我们数据特别大”,然后什么参数都不给。你可以拿下面这个表当需求调研模板。
| 参数项 | 示例值 |
|---|---|
| 设备数量 | 3000 台 |
| 平均低频测点 | 400 点/台,1Hz |
| 高频振动设备 | 30 台,32 通道/台,2kHz |
| 单条数据大小 | 低频约 40B,高频约 8B |
| 日新增原始数据 | 约 5.4TB |
| 压缩后日新增存储 | 约 500GB ~ 1TB |
| 写入峰值速率 | 120 ~ 300 万条/秒 |
有了这个表,设计保留策略就有依据。高频振动数据保留 30 天足够做故障分析,低频数据保留一年到三年,更老的数据用降采样后的小时级均值归档,原始明细可以清理。TDengine 的保留策略是建表时用KEEP参数控制的,比如高频表设置KEEP 30,低频表设置KEEP 1095,系统会自动删除过期数据,不用人工半夜起来清库。
3.4 写入调优与脏数据处理的实战心得
这一个小节是拿来给真正要碰生产环境的人看的。调优重点有三个:
第一,控制乱序率。TDengine 对乱序数据有合并机制,偶尔乱序没问题,但乱序比例高了会让写入性能明显下降。采集端必须保证同一子表的数据按时间戳递增堆送,网关断电重启后要能缓存断点期间的数据,恢复后按顺序补发。
第二,连接数管理。很多团队一上来就开一大堆写连接,反而把 vnode 搞得很紧张。经验值是:写线程数是 vnode 数量的 1 到 2 倍足够了,多用批量参数方式压单连接吞吐,少用多连接堆并发。
第三,数据质量码一定要入库。工业数据里“测得不准”和“设备故障”是两回事,要在字段里留一个 quality 列,写0表示正常、1表示传感器超限、2表示通讯中断补偿值。查询时可以忽略非正常质量的数据,又不会丢失原始信息。这个设计早期不做,后面数据清洗会让人哭都哭不出来。
4. 查询提速到“秒级”的机制与实战 SQL 拆解
标题里最让人兴奋的是“秒级”。但秒级不是变魔术,而是由存储结构、索引方式、计算下推共同决定的。理解这套机制,你才能在自己的项目里复现同样的效果。
4.1 秒级查询背后的四个引擎能力
第一是列式存储。TDengine 每列数据独立连续存储,查询只读取需要的列,比如算电流均值就只读motor_current那一列的数据块,其他几百列完全不碰,磁盘 IO 一下子少掉两三个数量级。
第二是时间分区裁剪。TDengine 的数据按时间自动分片,元数据里有每个数据块的起止时间范围。你查最近 5 分钟的数据,引擎直接定位到对应时间片,更早的分区连打开都不用。这就像查字典只看部首目录,而不是从第一页翻起。
第三是块内 min/max 索引。每个数据块在写入时就记录了该块内各列的最小值和最大值,区间查询时如果查询条件与块的 min/max 无交集,整块直接跳过。温度正常的设备,大多数块的 min/max 范围远小于全表区间,裁剪效率极高。
第四是聚合计算下推。查询时用 AVG、SUM、MAX 这些函数,不是把原始数据拉到应用层算,而是在数据节点直接从压缩后的块里读取、计算,只返回结果集。以前 MySQL 要 50 秒跑完的 1 小时均值聚合,TDengine 是在存储层按时间窗口直接预聚合的,秒回并不奇怪。
4.2 高频查询的 SQL 现场演练
我用三类最高频的查询做演示,你在自己的环境里可以直接改着用。
第一类是“这台设备现在是什么状态”,配监控大屏或状态看板。这种查询要的是每个测点的最新值,SQL 直接点子表并按 ts 倒序取一条:
SELECT last_row(motor_current), last_row(motor_temp), last_row(pump_pressure) FROM shearer_sec WHERE device_id = 'SH-001';第二类是“某台设备某个时间段的运行曲线”。直接走子表点查,时间范围用索引裁剪:
SELECT ts, motor_current, motor_temp FROM shearer_sec_001 WHERE ts >= '2025-06-01 08:00:00' AND ts < '2025-06-01 09:00:00';第三类是跨设备统计,比如按分钟统计一个矿井所有采煤机的电流均值,用超级表加标签过滤加时间窗口:
SELECT _wstart AS win_start, avg(motor_current) AS avg_current FROM shearer_sec WHERE mine_id = 'M01' AND ts >= '2025-06-01 00:00:00' AND ts < '2025-06-01 01:00:00' INTERVAL(1m);这类查询在 TDengine 上的表现,基本就是“数据量不影响查询速度,只影响扫描范围”,因为窗口聚合是逐块完成的,压缩过的数据解压也算增量开销,总体非常可控。
4.3 写入后立即读取:临时数据的可见性问题
网上经常有人问“TDengine 保存临时数据马上读取会不会读不到”,这个我专门实测过。TDengine 的写入流程是:数据先写内存和 WAL 日志,客户端收到成功返回即代表数据已经进入可查询状态,不需要额外调 FLUSH 才能读到。查询引擎看到内存中有这段数据就会直接返回,这在 3.x 版本上是很稳定的事。
真正会让你“读不到”的,反而是一些架构层面的坑。比如写入进程和查询进程如果共用同一个带本地缓存的连接,某些早期版本的客户端可能因为连接内事务一致性处理导致查询端看不到最新写入;换成服务器端查询或者重连基本就好了。另一个坑是时区不一致,应用服务器和 TDengine 服务端时区不同,写入时时间戳带偏移,查最近 1 分钟的数据就会出现“刚刚写入的查不到”的错觉。统一用 UTC 或者统一设置中国标准时间,问题就不存在了。
如果你对“崩溃不出问题”有硬要求,比如不能因为断电丢数据,那就在建库时设置 WAL 同步级别为强同步(wal_level 2),这样每次写入都要等 WAL 落盘再返回,吞吐会降一些,但数据安全等级更高,适合告警事件这类绝对不能丢的数据。
4.4 查询慢的真正原因排查思路
如果哪天你也遇到“TDengine 查询没有想象中快”的情况,不要慌,按顺序排查。最常犯的错是SELECT *一把梭,几百列读了个遍;其次是查询条件没有带时间范围,导致全量分区扫描;再就是聚合粒度太细,比如对一年数据做 1 秒级窗口,这种量级任何数据库都救不了,必须先降采样再聚合。还有一点要提醒,TDengine 的EXPLAIN命令可以看到执行计划,判断有没有走正确的标签过滤和时间裁剪。生产问题 80% 出在建模和 SQL 习惯上,而不是引擎本身。
5. 部署安装、集群配置与常见坑点排雷
最后说部署和运维。这部分的经验不只是从天地奔牛这类项目来的,也是我自己在实际环境里一步步踩出来的。
5.1 社区版安装与 Windows 环境的那点事
TDengine 社区版的安装,在 Linux 服务器上其实是非常省心的。官方提供 RPM 和 DEB 包,CentOS / Ubuntu 都能直接装,装完用systemctl start taosd启动服务,然后执行taos进命令行验证即可。如果是源码编译或者 tar 包,注意配置/etc/taos/taos.cfg里的firstEp和fqdn,单机测试默认配置通常不需要改。
Windows 环境我多说两句,因为网上讨论“tdengine windows 集群”特别多。TDengine 的服务端主力支持 Linux,Windows 上装服务端用于开发和功能验证没问题,但生产环境我强烈不建议在 Windows 上跑集群。Windows 集群在文件句柄管理、网络性能、服务守护这些方面都没有 Linux 成熟,稳定性差距明显。很多团队都是在 Windows 开发机上装了社区版测试 SQL 和功能,然后部署到 Linux 服务器跑生产,这没问题。Windows 上装客户端(taos shell、C/C++ 驱动)倒是很常规,连接 Linux 集群没有任何障碍。
5.2 生产环境问题排查速查表
我把这一路踩过的常见问题整理成了一张表,基本能覆盖运维初期 90% 的困扰。
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 写入报内存不足 | vnode 缓存配置过小或子表分布不均 | 调大 cache,检查表数量是否集中在少数 vnode |
| 刚刚写入的数据按时间查不到 | 时区设置不一致或查询窗口不对 | 统一服务端与客户端的时区配置,核对查询时间范围 |
| 大批量迁移后数据对不上 | 时间戳精度不一致(毫秒被截断或微秒被忽略) | 迁移时统一时间戳字段为毫秒级 INT64 |
| 高频振动数据写入变慢 | 乱序比例过高触发合并 | 采集端按时间戳排序后批量提交,乱序数据先本地排序 |
| C++ 连接在 Windows 上报 DLL 错误 | 编译器版本与连接器库不匹配 | 确认使用官方对应 MSVC 版本的连接器,并正确加载 taos.dll |
| 磁盘增长过快 | KEEP 保留策略未设置或未按频率分表 | 高频表保留 30 天,低频表设置 1~3 年 |
| 查询执行计划里全表扫 | SQL 没带设备标签过滤或时间范围 | 使用超级表标签过滤 + 明确的时间窗口 |
5.3 迁移过程中最容易忽略的四个细节
迁移项目大多不是死在技术难度上,而是死在细节上。第一个细节是建表顺序。不少人习惯先导数据再建索引,TDengine 恰恰相反,必须先建好超级表和子表,再导数据,否则每条数据都要现判断目标表存不存在,性能惨不忍睹。第二个细节是 CSV 文件里的时间格式,导入前按时间戳排好序,否则乱序处理会拖慢整个导入过程。第三个细节是账号权限,社区版默认的 root/taosdata 不要拿来做应用账号,应用账号按读写分离创建,避免误删和权限泄漏。第四个细节是监控告警,TDengine 的taosMonitor或外部 Prometheus 采集器最好从第一天就接上,磁盘空间、写入延迟、vnode 数量这些指标都有对应的 SQL 视图,别等出了问题才去翻日志。
迁移完成后的验收也不是查几个数就完了。我会建议做三类验证:写入性能压测(用taosBenchmark模拟生产写入速度)、查询性能抽样(把业务方反馈最慢的三条查询单独跑一遍)、数据完整性核对(按设备号和时间为维度,抽样比对 MySQL 和 TDengine 的 count 和 sum)。这三步过了,迁移基本就算稳了。
最后分享一点我个人在这个项目里的体会
工业时序数据的建设,工具选型当然重要,但真正的分水岭在于你有没有把数据模型想清楚。天地奔牛这个场景能跑到秒级,不是 TDengine 单独施了什么魔法,而是从采集端把测点分好了组、在建表时把标签和字段注释做规范了、在写入端把批量和乱序控制住了、在查询端把窗口聚合和过滤条件用对了,这四件事叠在一起才换来了最终的效果。
如果再往前看一步,这类 TB 级数据平台的价值绝不只在查询变快。数据进了 TDengine,等于把设备全生命周期的运行状态都沉淀下来了。后面接设备健康评分、故障预测、备件寿命分析,都是顺理成章的事。我个人的建议是,先跑通数据采集和秒级查询这个底座,再逐步往上加分析和预测应用。地基打牢了,上层建筑有的是发挥空间。