工业时序数据库选型:InfluxDB与TDengine对比实践
搞工业数据的人十有八九会被问到一个问题设备数据到底存哪儿这几年我在工厂里做设备数据采集从PLC点位到边缘网关再到数据平台一路跑下来光在时序数据库选型上就折腾了好几轮。用得最多的两个候选一个是InfluxDB开源早、社区大、Grafana支持顺滑几乎成了时序数据库的“默认选项”另一个是TDengine近几年在工业圈子里蹿得特别快主打高性能、低资源占用、超级表模型还经常拿自己的压测数据跟InfluxDB正面PK。这篇文章就是围绕“工业数据到底该存哪里”这件事把我在一套真实产线场景下对这两套系统做对比验证的完整过程梳理一遍。内容会包括数据规模估算、部署安装、写入链路改造、查询与复杂报表落地、资源成本核算以及我在现场踩过的那些坑。适合手里有大量设备点位、正在做时序数据库选型或者已经用了InfluxDB但被性能、成本和维护复杂度搞得头疼的团队。1. 选型之前先看清工业数据到底是个什么脾气很多选型项目失败不是因为数据库不行而是连需求都没拆清楚就急着装软件。先说结论工业数据跟互联网日志数据、App埋点数据看着都是“时间戳数值”但实际性格差异极大这直接决定了InfluxDB和TDengine谁更合适。1.1 第一张身份证高频写入数据量按“亿级/天”算工业现场最常见的采集场景是PLC和DCS点位。PLC一个扫描周期可以做到几十毫秒到几百毫秒DCS控制站一般秒级采集加上传感器直接走4-20mA或Modbus TCP进网关的情况折算下来一台中型产线一天产生几千万到上亿个点位值非常正常。以我后文要讲的项目为例50台设备、2000个点位、200ms采集周期理想状态下每秒写入就是10000条数据一天约8.6亿条连续存三年就是接近900多亿条。这个量级对任何数据库都是实打实的压力不是随便拿个MySQL就能扛的。InfluxDB和TDengine在设计上都是冲着这个场景来的但处理策略完全不同。InfluxDB把数据按时间分片用TSM存储引擎和内存索引加速写入TDengine则是以“时序数据天然按时间有序”为核心把存储、索引、计算绑定到时间维度上所以写入路径更短。这一点在后面的压测里会有明显体现。1.2 第二张身份证点位固定标签清晰但“时间线”数量爆炸工业设备点位有一个重要特点数量大但属性相对固定。比如一台电机的电流、温度、振动、转速这些监测点就是几个固定标签值随时间变化。看起来比互联网用户行为数据简单对吧可一旦点位多了真正的麻烦就来了——数据库里的“时间线/序列”数量会飞速增长。InfluxDB把一组标签组合视为一个序列2000个点位如果每个点位还有设备号、工位号、设备类型几个标签序列数会很容易冲到几万甚至几十万。这也是我在实际项目中第一个踩到的问题InfluxDB对高基数场景非常敏感序列数一多内存和索引开销直线上升。TDengine在这个问题上换了个思路用“超级表子表”把设备的静态标签和动态采集量分开静态标签只存一份动态数据按时间存储相对没那么容易失控。后面我会详细讲超级表的具体用法这里先记住点位越多标签维度越多两种库的设计差异就越明显。1.3 第三张身份证写多读少但每次查询都是“区间聚合”工厂里对实时数据的使用说白了就这几类监控大屏看当下实时值、历史曲线回放、班组产量统计、设备OEE指标计算、故障前后几分钟的追忆。前两类要求低延迟后几类本质都是时间范围内的聚合计算比如count、avg、sum、max、min。没有哪个业务会像互联网应用那样频繁地用主键去查某一条历史记录。所以选型时“单点查询能力”和“随机删除能力”并不重要重要的是三点高吞吐写入、区间扫描效率、聚合计算下推能力。InfluxDB有连续查询和强大的Flux/InfluxQL聚合语法TDengine有内置的窗口函数和超级表聚合还可以建流式计算。两者都能干但实现路径和开发体验差异很大我用项目经验说。1.4 选型前先回答五个问题不然后面全是坑在正式开始对比之前我建议任何团队都先拿一张纸把这五件事写清楚数据规模点位多少、采集频率多少、要存多久这决定预留磁盘和内存。写入方式直接SQL还是时序协议数据要不要过滤、清洗后入库查询报表是只要曲线图还是要做复杂的多维聚合、TopN、同比环比部署环境工厂内网还是云上有没有Docker/K8sWindows下有没有要求团队维护能力有没有专职DBA出问题能找谁数据库挂了影响多大把这五个问题答完再来看InfluxDB和TDengine的文宣参数你会冷静很多。不能因为某个数据库在某篇博客里“压测跑分很高”就无脑上车那个跑分场景多半是你永远遇不到的理想环境。2. 内功心法两套数据库的架构和设计思路差在哪这一节我尽量用大白话拆解InfluxDB和TDengine底层是怎么存数据、建索引、做查询的。搞懂这些部署和调优时才会有方向感否则只会照着教程敲命令出了问题完全抓瞎。2.1 InfluxDB的看家本领TSM引擎加上倒排索引InfluxDB从1.x时代就确立了“时间分片内存索引列式压缩存储”的基本盘。新数据来了先写WAL预写日志同时更新内存缓存到达一定阈值后Flush成不可变的TSM文件后台再定期做Compaction合并。这套流程借鉴了传统LSM-Tree的思路对随机写入非常友好但它有个副作用时间线一旦很多内存里的索引会变得很大很容易出现“数据量看着不高可用内存居高不下”的情况。InfluxDB对标签字段会建倒排索引方便你按设备号、工位地去查。功能是真好用尤其做条件过滤时一句SQL就能筛出某个设备的所有序列。可代价也摆在明面上基数cardinality越高索引膨胀越快内存越大。我在生产环境里见过只有几百GB数据、内存却占掉30多GB的InfluxDB实例一问全是标签组合爆炸惹的祸。2.2 TDengine的底层逻辑数据天生有序直接上列式存储TDengine的出发点不太一样。它认为时序数据本身就是按时间排列的没必要为了追求通用性给每条记录做复杂的索引结构。数据先按时间段分片vnode每个分片内部按时间递增排列用列式存储压缩同一列的数值标签单独存储并建立索引。写数据时本质上就是“追加”操作所以单机写入吞吐非常有优势。更妙的是“超级表”STable概念。一个超级表代表一类设备或一组采集对象比如“设备状态表”下面按设备ID建子表每张子表继承超级表的结构标签放在超级表里。查询时直接对超级表做聚合系统自动跨子表并行扫描。你不需要像InfluxDB那样时刻盯着序列数因为标签和采集量在物理上是分开的基数高一些也没那么怕。我自己的体会是TDengine更适合点位多、设备数量大、标签模型稳定的工业场景。2.3 数据模型measurement、tag/field 和超级表的对比来点实际的。InfluxDB写入一行数据结构是这样的measurement,tag1xxx,tag2yyy field11.1,field22.2 时间戳比如device_status,device_idDEV001,shopSHOP_A temperature67.2,rpm1450 1700000000000000000这里“device_status”是表名device_id、shop是标签索引字段temperature、rpm是字段数值字段。如果再加一个工位标签序列数就翻一倍因为InfluxDB按标签组合区分时间线。TDengine的建表方式则完全不同。先建库再建超级表然后自动或手工建子表。一段典型的建表语句CREATE DATABASE factory KEEP 3650 DURATION 30 BUFFER 16 WAL_LEVEL 2; CREATE STABLE device_status (ts TIMESTAMP, temperature DOUBLE, rpm DOUBLE) TAGS (device_id BINARY(32), shop BINARY(16)); CREATE TABLE DEV001 USING device_status TAGS (DEV001, SHOP_A);注意子表的表名就是设备编号它对应InfluxDB里的一条时间线。以后查询一般只碰超级表也可以用WHERE device_idDEV001过滤。这个模型的优势是标签列全局只有一份采集数据按子表分散存储聚合和检索效率高表的数量可控。缺点是如果工业点位标签经常变化你需要维护超级表的版本不像InfluxDB那么“想加标签就加”。从日常运维角度看InfluxDB的数据模型灵活适合快速接入各式各样的半结构化数据TDengine适合“设备种类固定、点位明确”的工业现场。我这里没有说谁好谁坏我只说如果你想用TDengine最好先梳理清楚设备和点位的标签模型因为超级表的优势要建立在稳定的元数据之上。2.4 降采样和连续计算工业报表的刚需能力工业报表离不开“降采样”。原始数据200ms一条画一个月的曲线根本没意义你需要的是按分钟、小时统计的均值、最大值、最小值。InfluxDB的解决方案是Continuous Query连续查询可以定时把降采样结果写入到另一张保留策略不同的表里。配合Retention Policy你能实现“明细数据保留30天分钟级数据保留一年小时级数据保留三年”。我在早期项目里这么干确实有效但是CQ数量和查询复杂度上去之后管理起来有点乱写错了也不好调试。TDengine这边让你少建很多“中间表”。它支持在SELECT里直接写时间窗口函数比如SELECT _wstart, avg(temperature), max(rpm) FROM device_status INTERVAL(1m) GROUP BY device_id;一次查询直接得到每分钟聚合结果不需要预先建连续查询任务。更进一步TDengine的流计算流表可以把聚合结果实时写入另一张表相当于把“实时计算落库”合成一步特别适合滚动统计产量或计算设备OEE这类场景。我在实际项目中用流计算替换掉了原来InfluxDB里一堆CQ任务维护成本明显下降。这一点对团队人力不算充裕的项目尤其加分。3. 同一条产线我把两套库都跑了一遍理论讲再多不如直接拿数据说话。我这边有一个做汽车零部件加工的产线项目下面就用这个项目作为真实对比样本。所有结果不带任何“官方压测”光环就是我们在普通服务器上跑出来的感受。3.1 现场场景和数据规模估算项目基本情况50台数控设备每台设备采集点位约40个合计2000个点位。数据频率200ms一条。除了设备数据还有少量环境传感器数据温度、湿度、能耗频率1秒一条。需要存储三年支持滚动一年前的任意时间段回溯和分钟级报表。数据量算一下每秒约2000/0.210000条写入每天86400秒约8.64亿条三年就是约946亿条。如果按每条原始记录约50字节计算时间戳、数值、标签开销折算一年原始数据约1.5TB左右。加上索引、副本和降采样数据两套库都必须认真对待存储规划。我们准备的测试服务器配置32核64G内存两块2TB NVMe SSDCentOS 7.9内网环境。3.2 部署和初始化从安装包到第一行数据先装TDengine。社区版从官网或GitHub Release下载tar包解压后执行install脚本然后启动taosd服务。TDengine 3.x版本还引入了taosAdapter这是一个独立进程提供RESTful接口、支持Grafana通过HTTP方式访问需要单独安装并启动。初始化数据库taos CREATE DATABASE factory KEEP 3650 DURATION 30 BUFFER 16 WAL_LEVEL 2; USE factory; CREATE STABLE device_status ...;这里几个参数值得解释一下。DURATION 30是数据在vnode里按30天一个时间片滚动存储时间片到期后自动合并压缩适合长期海量写入。BUFFER 16是每个vnode的内存池大小单位MB太大会吃内存太小会导致写入不稳。WAL_LEVEL 2是写WAL的级别级别越高越安全但性能稍降。生产环境我建议WAL_LEVEL设为2别为了追求性能省这点IO。再装InfluxDB。我们用的是2.7 OSS版本可以通过apt/yum直接装也可以拉Docker镜像。InfluxDB 2.x引入了bucket、token、Flux的概念初次启动后需要去Web UI创建admin用户、初始bucket和API Token。命令行下面也可以先快速建bucketinflux setup --username admin --password password --org factory --bucket factory_data --retention 0 --token my-token注意InfluxDB 2.x的retention 0代表永久保留不要和1.x里的RP混为一谈。如果后续要用Grafana数据源地址填http://ip:8086需要在数据源配置里填一个带读写权限的API Token。部署过程我踩过一个坑TDengine启动后taosAdapter没有默认随系统启动导致Grafana连不上重启服务器后数据库数据源一直报错。后来把taosAdapter注册成systemd服务就好了。InfluxDB这边反而是服务管理做得更省心装完就自动注册好了。3.3 写入链路Python客户端实测对比现场数据采集程序是用Python写的从网关拿到Modbus TCP解析后的点位数据批量写入数据库。为了减少网络交互我们按1000条一批做批量写入。InfluxDB 2.x的Python客户端写法很“标准”from influxdb_client import InfluxDBClient, Point from influxdb_client.client.write_api import SYNCHRONOUS client InfluxDBClient(urlhttp://10.0.0.11:8086, tokenmy-token, orgfactory) write_api client.write_api(write_optionsSYNCHRONOUS) points [] for item in data_list: p Point(device_status) p.tag(device_id, item.device_id) p.tag(shop, item.shop) p.field(temperature, item.temperature) p.field(rpm, item.rpm) p.time(item.timestamp_ns) points.append(p) write_api.write(bucketfactory_data, recordpoints)每批1000条服务器在默认配置下能稳定写入每秒1.2万条左右。如果并发太高InfluxDB会返回too many requests需要客户端做背压我们后来把写批次改成5000条并开启异步写性能才上去。TDengine的Python客户端taospy则需要用参数绑定来获得最优写入性能import taos conn taos.connect(host10.0.0.12, userroot, passwordtaosdata, databasefactory) sql INSERT INTO device_status VALUES ? rows [] for item in data_list: rows.append((item.timestamp_us, item.temperature, item.rpm, item.device_id, item.shop)) # 实际用stmt绑定逐项指定子表 conn.execute(INSERT INTO DEV001 USING device_status TAGS(DEV001,SHOP_A) VALUES (?,?,?), ...)我这里省略了完整的绑定代码只展示关键思路。同样一批1000条数据TDengine的完整批写入响应时间明显更短。在我们的测试机里相同条件下TDengine单节点写入稳定在每秒2.5万条以上峰值高的时候接近4万条而且写入期间CPU占用比InfluxDB低。不过要说明InfluxDB我们只用默认配置没有做tsm分片大小和缓存参数的深度调优所以这个对比只代表“开箱即用”的水平。3.4 查询和可视化从Grafana看图说起工厂值班人员最常用的就是看大屏和历史曲线。我们的习惯是统一走Grafana不额外开发Web界面。Grafana支持InfluxDB数据源差件生态非常成熟这个没什么悬念选InfluxDB数据源、填URL和Token、浏览bucket直接出图。TDengine官方也提供了Grafana数据源插件支持通过taosAdapter的REST接口查询配置起来大体类似。让我说说实际查询手感。在Grafana里查“过去24小时1号设备温度曲线”InfluxDB返回原始序列没有问题。但当你跨40台设备做“每5分钟平均温度”对比视图时Flux查询语句写起来会有些绕。比如from(bucket: factory_data) | range(start: v.timeRangeStart, stop: v.timeRangeStop) | filter(fn: (r) r[_measurement] device_status) | filter(fn: (r) r[_field] temperature) | aggregateWindow(every: 5m, fn: mean, createEmpty: false)这种写法灵活但学习成本和排错成本真不低。要么你熟练掌握Flux要么你依赖图形化查询编辑器。TDengine这边直接写SQL就行SELECT _wstart, avg(temperature) FROM device_status WHERE ts now-24h INTERVAL(5m) GROUP BY device_id;字段、窗口、分组一目了然。组态软件、MES厂商的工程师普遍都会SQL接入成本低非常多。我们对MES厂商提需求时对方直接复制SQL就联调了根本不需要他们先学一遍Flux。如果不想用Grafana两家也都有自己的可视化工具。InfluxDB 2.x自带Data Explorer和仪表盘TDengine 3.x带一个类似于数据库管控台的Web界面现在叫TDengine Studio一类工具可以看集群状态、执行SQL、做简单图表。以我们工厂运维人员的反馈来说TDengine的管控界面更“数据库化”熟悉传统数据库的人用起来更顺手InfluxDB的界面更“云原生”但现场老师傅看着有点懵。3.5 资源占用和成本账为什么有人觉得TDengine“太贵”先放实测数据同一份约1.2TB原始数据在InfluxDB里磁盘占用约600GB含索引和WAL在TDengine里约350GB主要是因为列式存储和更好的压缩算法。内存方面InfluxDB高基数下峰值占用到25GB而TDengine在这套数据量下实际只用了不到8GB。注意这个差距在我们把点位从2000扩到8000后更明显——InfluxDB出现明显的内存换页我不得不限制查询并发。说到成本这里必须回应一个热搜词“tdengine太贵了”。我看到网上不少人在问TDengine是不是收费很贵其实得分清楚说话的人处于什么阶段。TDengine社区版完全免费且包含集群能力、水平扩展、数据复制这些核心功能只是缺少企业级运维工具和官方售后支持。如果你拿社区版和InfluxDB OSS比TDengine免费功能反而更多。所谓“贵”多半是企业版或者服务合同的价格。但InfluxDB企业版同样不便宜而且如果要用集群和冷热分层一样是按规模付费。我们的经验是预算有限、愿意自己背锅的团队用TDengine社区版足够真出了大故障需要原厂兜底再考虑企业版。而不是一上来就买企业版那当然贵。4. 实战中踩过的坑排障与取舍实录任何生产系统都不可能是完美的。我在两套数据库上都用过不少时间把典型问题整理成速查表希望能让你少走弯路。4.1 关于InfluxDB我要说的三个老兵愁第一个是“高基数内存爆掉”。项目早期我们用InfluxDB 1.82000个点位的序列数在写满3个标签后冲到21万节点频繁OOM。加内存只是短暂缓解最后靠重构标签、把一部分字段转成传统关系库才稳住。现在InfluxDB 2.x/3.x内存管理好一些但高基数依然是首要调优点。建议控制标签维度尽量只放常用的过滤标签数值变化频繁的标签别放索引。第二个是“数据删除和更新极其别扭”。InfluxDB的存储设计不太适合高频删除删除时间范围大的数据会触发大量compaction影响查询性能更新一条历史值更是基本没法做。工业场景经常有仪表校验、数据补录的需求我们后来是宁可写入一条新记录并标记覆盖值也不敢去硬删。第三个是“Flux脚本难调试”。Flux作为查询语言功能确实强但报错信息比较抽象尤其管道函数顺序写错时排查耗时。我的建议是如果团队普遍不熟悉函数式语言尽量用InfluxQLInfluxDB 2.x虽然在退化InfluxQL支持但一般查询够用。4.2 关于TDengine常见翻车现场有哪些先说说“Windows集群”这个热搜词。TDengine的taosd和taosAdapter都支持Windows但Windows下进程管理和Linux不太一样很容易遇到taosAdapter没有拉起、端口被占用、防火墙挡了6030/6041端口等问题。Windows集群如果只是双节点需要特别注意域名解析所有节点都要能通过主机名互相访问才行。建议部署前先在所有节点hosts里加好条目避免动态DNS坑。第二个常见问题是“时间戳单位搞混”。TDengine的默认时间戳精度是毫秒但你写入纳秒数据时不显式指定会被当成毫秒导致时间偏移或者直接报“invalid timestamp”。我们统一在创建数据库时指定精度比如PRECISION ms或者PRECISION us并让采集程序统一换算避免混用。第三个问题是“子表数量过多或者过少的管理失误”。有些人懒得为每个设备建子表把设备号也塞进时间列;还有人所有点位都放到同一张子表查询时靠WHERE硬筛。前者失去了超级表聚合能力后者会导致单一子表膨胀巨大。正确做法是每个设备一张子表点位对应超级表的列少用动态标签。最后一个全网都在提的“TDengine太贵了”误解我在成本那节已经解析过。这里再补一句如果你从InfluxDB切到TDengine可能会觉得“怎么什么都要自己配服务器参数”实际上不是TDengine收费而是它的文档和默认配置更偏“高性能数据库”需要你花时间理解vnode、wal、cache这些概念。习惯了传统关系库的人一开始会觉得陡峭。4.3 值班速查到底该选谁总结一个决策清单纯经验向不代表绝对真理场景特征建议团队熟悉SQL现场工程师要多方协作TDengine更顺超级表和标准SQL降低沟通成本数据模型经常变喜欢灵活加标签InfluxDB更灵活写点模型想加字段就加点位超过5000长期存原始数据首选TDengine压缩率高内存占用低重度依赖Grafana生态和Flux图表InfluxDB更加无缝但需接受高基数成本需要Windows集群没有专职Linux运维TDengine可以Windows部署但要有排查能力预算敏感又要集群、副本、核心功能TDengine社区版免费含集群InfluxDB集群要企业版已有大量InfluxDB时序代码和工具链迁移成本高建议先用起TDengine做新点位的增量如果你让我给一个通用答案中小型工业物联网项目几百个点位、单一工厂、团队经验一般用InfluxDB OSS单节点完全够用点位几千、数据量大、未来还会加设备、或者想省服务器资源认真评估TDengine社区版。组件化的时间线管理、存储分层和流计算确实是为工业场景生的。但如果你已经有了一支熟练使用Flux的BI团队那就不用折腾迁移把InfluxDB的系列数控制好就行。最后再分享一点个人的实际体会换数据库不是换发动机是换整车。不要被任何一方的压测报告带走拿自己的数据、自己常用的查询在内网服务器上跑一个礼拜看内存、看磁盘、看写入稳定性、看同事们的学习成本再定结论。我在这个项目里最后把主存储切到了TDengine但保留了InfluxDB作为临时数据缓冲和分析试验田两套库配合使用也算是一种务实的选择。先把业务跑起来比一下子押注在某一个“标准答案”上重要得多。