4、数据存储方案:时序数据库选型(InfluxDB/TDengine)、关系型数据库设计、数据压缩策略

说到数据存储,我得先跟你交个底——电表数据这玩意儿,跟普通业务数据完全是两码事。

你想想看,一个中型园区,几百个电表,每15秒采集一次。一天下来就是几百万条记录。一年呢?几个亿。这还只是电表,还没算上断路器状态、变压器温度、光伏逆变器数据……

所以,选存储方案,说白了就是选「活路」。选错了,后面查询慢、存储贵、维护难,哭都来不及。

核心结论先放这:

  • 时序数据(电表读数、功率、电压)→ 时序数据库
  • 配置数据(电表参数、用户信息、设备台账)→ 关系型数据库
  • 历史归档数据 → 压缩策略 + 冷热分离

4.1 时序数据库选型:InfluxDB vs TDengine

时序数据库,就是专门为「带时间戳的数值」设计的。普通数据库存这种数据,效率低得吓人。

我这些年用过InfluxDB,也深度用过TDengine。各有千秋,我跟你聊聊真实感受。

4.1.1 InfluxDB:老牌劲旅,生态成熟

InfluxDB 1.x 版本我用了三年多。说实话,上手是真的快。

  • 安装简单:一条命令搞定,不用折腾集群
  • 查询语法友好:类SQL的InfluxQL,工程师基本零门槛
  • 数据保留策略(RP):自动过期删除,省心
  • 连续查询(CQ):自动降采样,把原始数据聚合成分钟级、小时级

但坑也有。我记得有一次项目,电表数量从500个扩展到2000个,InfluxDB的写入性能直接崩了。后来查了半天,发现是磁盘IO扛不住了。InfluxDB的单机版,写入瓶颈很明显。

避坑指南:我曾经在某个光伏监控项目里,用InfluxDB存逆变器数据。一开始好好的,三个月后查询越来越慢。后来发现是tag(标签)设计得太细了——每个逆变器ID都当tag,导致series基数爆炸。记住:tag的基数不要超过100万,否则性能断崖式下跌。

4.1.2 TDengine:国产新秀,性能怪兽

TDengine是涛思数据的产品。我第一次接触是2020年,当时抱着试试看的心态。

结果一用就回不去了。为什么?

  • 写入速度极快:单机每秒能写几百万条,InfluxDB的5-10倍
  • 压缩比惊人:我实测过,原始数据压缩后只有原来的1/7到1/10
  • 超级表(Super Table):这个设计太聪明了。一张超级表定义结构,每个电表自动创建子表,查询时自动聚合
  • SQL兼容:直接写标准SQL,不用学新语法

不过,TDengine也有短板。它的生态不如InfluxDB丰富,可视化工具少一些。而且,如果你要存非时序数据(比如字符串日志),它就不太合适了。

对比维度 InfluxDB TDengine
写入性能 中等(单机约10万点/秒) 极高(单机百万级)
压缩比 约3:1 约7:1 ~ 10:1
查询语法 InfluxQL / Flux 标准SQL
集群能力 企业版才支持 开源版支持
学习成本 极低(会SQL就行)
适用场景 中小规模、快速原型 大规模、高并发、工业级

我的建议:如果你是新项目,电表数量超过1000个,直接上TDengine。如果只是几十个电表做实验,InfluxDB更省事。

4.2 关系型数据库设计:别把所有鸡蛋放一个篮子里

时序数据库管「数据」,关系型数据库管「元数据」。

什么是元数据?电表的型号、安装位置、所属用户、倍率参数、通信协议……这些信息变化频率低,但需要关联查询、事务支持。这就是关系型数据库的活儿。

我个人习惯用PostgreSQL。MySQL也行,但PostgreSQL对JSON、数组、地理信息的支持更好,做能源管理系统很合适。

4.2.1 核心表设计

我直接给你看一个实际项目的表结构,你照着抄都行。

-- 电表台账表
CREATE TABLE meter_asset (
    meter_id        VARCHAR(32) PRIMARY KEY,      -- 电表ID,如 'MTR-2024-001'
    meter_name      VARCHAR(64) NOT NULL,          -- 电表名称
    meter_type      VARCHAR(16),                   -- 类型:三相/单相
    install_location VARCHAR(128),                 -- 安装位置
    ct_ratio        DECIMAL(10,2) DEFAULT 1,       -- 电流互感器变比
    pt_ratio        DECIMAL(10,2) DEFAULT 1,       -- 电压互感器变比
    comm_protocol   VARCHAR(16),                   -- 通信协议:Modbus/DLT645
    comm_address    VARCHAR(32),                   -- 通信地址
    status          SMALLINT DEFAULT 1,            -- 状态:1正常 0停用
    created_at      TIMESTAMP DEFAULT NOW()
);

-- 用户-电表关联表
CREATE TABLE user_meter_rel (
    user_id     VARCHAR(32) NOT NULL,
    meter_id    VARCHAR(32) NOT NULL,
    role        VARCHAR(16) DEFAULT 'owner',       -- owner/viewer
    PRIMARY KEY (user_id, meter_id)
);

-- 采集任务配置表
CREATE TABLE collect_task (
    task_id     SERIAL PRIMARY KEY,
    meter_id    VARCHAR(32) NOT NULL,
    interval_sec INT DEFAULT 15,                   -- 采集间隔,单位秒
    data_items  JSONB,                             -- 采集项,如 ["voltage","current","power"]
    is_active   BOOLEAN DEFAULT TRUE
);

嗯,这里要注意:千万别把时序数据往关系型数据库里塞。我见过有人把电表读数直接存MySQL,一个月后表就有几千万行,查询一条数据要好几秒。这不是找罪受吗?

4.3 数据压缩策略:省钱省空间

数据压缩,说白了就是「用时间换空间」。但压缩策略选得好,空间省了,查询速度还不慢。

4.3.1 时序数据库自带的压缩

无论是InfluxDB还是TDengine,底层都用了列式存储+压缩算法。

  • InfluxDB:使用Facebook的ZSTD算法,对浮点数压缩效果不错
  • TDengine:自研的压缩算法,针对工业时序数据做了优化。我实测过,同样的电表数据,TDengine的存储空间只有InfluxDB的1/3

4.3.2 降采样(Downsampling)

这是最实用的压缩策略。没有之一。

原始数据是15秒一条,但做日报、月报时,你根本不需要这么高的精度。

-- TDengine 示例:创建降采样视图
CREATE TABLE meter_data_hourly AS
SELECT 
    _wstart AS time_bucket,
    meter_id,
    AVG(power) AS avg_power,
    MAX(power) AS max_power,
    MIN(power) AS min_power,
    SUM(energy) AS total_energy
FROM meter_data
INTERVAL(1h);

你看,原始数据如果一天有5760条(15秒间隔),降采样到小时级就只剩24条。存储量直接降到1/240。

我的经验:保留三份数据——原始数据保留7天(用于故障分析),分钟级数据保留3个月(用于日常监控),小时级数据保留3年(用于报表和审计)。这样既保证了查询速度,又控制了存储成本。

4.3.3 死区压缩(Deadband Compression)

这个技巧可能很多人不知道。电表数据有个特点:大部分时间变化很小。比如电压,稳定在220V左右,波动不超过±2%。

死区压缩的思路是:如果新数据跟上一个数据相比,变化不超过阈值,就不存。

// 伪代码示例
function shouldStore(newValue, lastValue, deadband) {
    // 死区为0.5%,变化超过0.5%才存储
    return Math.abs(newValue - lastValue) / lastValue > deadband;
}

我曾经在一个工厂项目中用了这个策略,数据量直接减少了60%。而且对分析结果几乎没有影响——因为微小的波动本来就不重要。

注意:死区压缩不适合计费数据。计费要求精确到小数点后两位,任何数据都不能丢。所以,计费数据单独存,不做任何有损压缩。

4.4 整体架构图

下面这张图,是我做过的某个EMS项目的存储架构。你一看就明白了。

EMS数据存储架构 数据采集层 电表采集终端 | 网关 | 协议解析 (Modbus/DLT645/IEC104) 消息队列 (Kafka / RabbitMQ) 削峰填谷,保证数据不丢失 时序数据库 InfluxDB / TDengine 原始数据 + 降采样数据 关系型数据库 PostgreSQL / MySQL 设备台账、用户、配置 对象存储 MinIO / S3 历史归档、日志、备份 应用层 实时监控 | 数据分析 | 报表系统 | 告警服务 | EMS控制逻辑

这张图里,数据从采集层进来,先经过消息队列缓冲,然后分三路存储:

  • 时序数据库:存实时数据和降采样数据,供监控和短时分析
  • 关系型数据库:存设备信息和用户配置,供业务系统查询
  • 对象存储:存超过3个月的历史归档,压缩后存成Parquet文件,成本极低

一个小技巧:对象存储里的历史数据,可以用Apache Parquet格式存。Parquet是列式存储,压缩比高,而且支持按列读取。我试过,同样的数据,Parquet比CSV小了80%,查询速度还快了10倍。

好了,数据存储这块就聊到这儿。选型没有绝对的对错,关键看你的场景。电表数量少,InfluxDB省心;数量大、要求高,TDengine更香。关系型数据库老老实实存元数据,别越界。压缩策略用好了,存储成本能降一大半。

记住一句话:数据是能源管理的血液,存储方案就是血管。血管堵了,整个系统都得瘫痪。


公众号:蓝海资料掘金营,微信deep3321