PLC 监控系统数据持久化方案:MySQL + SQLite 双保险,不丢数据
工业监控中数据丢失是最致命的。本文详解 PLCMonitor 的双数据库架构:MySQL 主存储 + SQLite 本地缓存,网络故障时自动降级,恢复后自动同步。
问题:你的产线数据打算存多久?
很多工业监控项目上线后才发现一个问题:数据说没就没了。
| 场景 | 后果 |
|---|---|
| 上个月温度曲线异常,数据已经清了 | 找不到证据,排查延长 |
| 月底要出生产报表,历史数据丢了 | 管理层不高兴 |
| 数据库文件损坏 | 三个月白干了 |
| 停电了 | 没电时采的数据,永远没了 |
工业监控的数据存储,不是"能存就行",而是要做到:
- ✅ 不丢数据(容灾)
- ✅ 存得住(容量管理)
- ✅ 查得快(性能)
- ✅ 随时恢复(备份)
为什么用两个数据库?
PLCMonitor 同时使用SQLite和MySQL,分工明确:
| 数据库 | 角色 | 特点 |
|---|---|---|
| SQLite | 本地缓存 + 故障切换 | 单文件、免安装、离线可用 |
| MySQL | 主存储 | 并发支持、远程访问、多客户端共享 |
核心设计理念:优先 MySQL,失败走 SQLite
采集数据 ↓ 优先写入 MySQL(主库) ↓ 成功? ├─ YES → 记录 synced=1 └─ NO → 写入 SQLite(备库) ↓ MySQL 恢复后 SQLite → MySQL 自动同步效果:
- 正常运行时:数据存在 MySQL,多客户端可查
- 网络故障时:数据不会丢,缓存在 SQLite
- 网络恢复后:积压数据自动补传
为什么不选其它数据库?
| 数据库 | 为什么不选 |
|---|---|
| PostgreSQL | 功能强大但重量级,部署成本高 |
| MongoDB | 文档型,时序数据量巨大时查询性能不如关系型 |
| InfluxDB | 专用时序数据库,门槛高,社区版不支持双写降级 |
| Redis | 内存数据库,断电即丢,不适合长期持久化 |
| SQL Server / Oracle | 企业级商业数据库,授权费数万起步 |
结论:SQLite + MySQL 组合兼顾离线可用和远程共享,成本最低、部署最简单。
数据量计算:500 标签每秒采集
假设场景:500 个标签,每秒采集一次。
| 时间维度 | 记录数 |
|---|---|
| 每秒 | 500 条 |
| 每分钟 | 3 万条 |
| 每小时 | 180 万条 |
| 每天 | 4320 万条 |
| 每月 | 13 亿条 |
| 每年 | 157 亿条 |
以每条记录约 40 字节计算,每年原始数据约 620 GB。
存储不是问题,问题在查询。
为什么不能直接查原始数据?
查询过去一年温度趋势:
- 一年 157 亿条数据
- 筛选温度标签 → 至少千万级扫描
- 全表扫描,一次查询 30 秒到数分钟
时间越长,需要的不是原始数据,而是聚合数据。
聚合策略:时间越长,粒度越粗
工业监控的本质是看趋势,而不是看瞬时值。
| 查询目的 | 时间跨度 | 需要的精度 |
|---|---|---|
| 实时报警 | 1 秒 | 精确到每个采样点 |
| 当前状态 | 1 分钟 | 最近值足够 |
| 今天趋势 | 1 小时 | 每分钟一个点即可 |
| 本周分析 | 1 天 | 每小时一个点即可 |
| 年度总结 | 1 个月 | 每天一个点即可 |
聚合表设计
| 粒度 | 表名 | 数据源 | 触发间隔 | 保留时长 | 聚合规则 |
|---|---|---|---|---|---|
| 秒级 | data_second | PLC 采集 | 实时 | 7 天 | 原始值 |
| 分钟级 | data_minute | 秒级数据 | 每分钟 | 30 天 | 平均值 + 最后值 |
| 小时级 | data_hour | 分钟数据 | 每小时 | 1 年 | 平均值 + 最后值 |
| 天级 | data_day | 小时数据 | 每天 | 永久 | 平均值 + 最后值 |
什么是"平均值 + 最后值"?
以温度为例,一分钟内 60 次采样:
| 时间 | 14:00:01 | 14:00:02 | … | 14:00:59 |
|---|---|---|---|---|
| 原始值 | 25.1 | 25.3 | … | 25.4 |
聚合后变成一条记录:
timestamp: 2024-06-01 14:00:00 avg_value: 25.2 last_value: 25.4好处:查一年数据只需查data_hour表(438 万条),而不是data_second(157 亿条)。
EAV 模型:标签随意增减
传统方式的问题
每加一个标签就要改表结构——维护噩梦。
EAV 方案
EAV =Entity-Attribute-Value(实体-属性-值),动态标签友好:
CREATETABLEdata_second(idINTEGERPRIMARYKEYAUTOINCREMENT,data_idBIGINTNOTNULL,-- 全局唯一 IDtag_nameVARCHAR(255)NOTNULL,-- 标签名valueDOUBLENOTNULL,-- 值qualityTINYINTDEFAULT0,-- 质量(0=好,1=坏)timestampDATETIMENOTNULL,-- 时间syncedTINYINTDEFAULT0-- 同步标记);无论加多少标签,表结构不动。
data_id使用(当前毫秒时间戳 << 20) | 随机数生成,确保跨机器、跨时间唯一。
写入优化:异步批量
问题:500 次单独 INSERT 太慢
每秒 500 个标签,每条单独插入:
- 500 次 SQL 解析
- 500 次磁盘 IO
- 500 次锁竞争
太慢了。
方案:队列 + 批量写入
┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │ PLC 采集线程 │ ──→ │ 异步队列 │ ──→ │ 数据库工作线程 │ │ (每秒一次) │ │ QQueue │ │ 每 200ms 处理 │ └─────────────┘ └─────────────┘ └──────┬───────┘ │ 批量 500 条 INSERT具体做法:
- PLC 采集线程把数据放入异步队列
- 数据库工作线程独立运行,每 200ms 取出一批
- 最多 500 条一批,用预处理语句批量写入
- 成功写入 MySQL 后标记
synced=1
500 次插入变成 1 次批量插入,性能提升数百倍。
主备切换:MySQL 挂了怎么办?
步骤 1: 尝试写入 MySQL 步骤 2: 失败 → 标记 MySQL 不可用 步骤 3: 下次写入直接走 SQLite 步骤 4: 每 30 秒检测 MySQL 是否恢复 步骤 5: 恢复后,自动同步积压数据历史查询:自动选表
根据查询跨度,系统自动选择最优表:
| 时间跨度 | 查询表 | 原因 |
|---|---|---|
| ≤ 7 天 | data_second | 精度高 |
| ≤ 30 天 | data_minute | 数据量少,查询快 |
| ≤ 1 年 | data_hour | 天级精度足够 |
| > 1 年 | data_day | 长期趋势 |
查询性能
| 场景 | 数据量 | 响应时间 |
|---|---|---|
| 查最近 1 小时 | 3 万条 | < 100ms |
| 查最近 7 天 | 8.4 万条 | < 200ms |
| 查最近 1 年 | 438 万条 | < 2s |
关键是聚合表数据量小,且命中索引。
数据清理:不越存越多
每日凌晨自动执行:
-- 清理 7 天前的秒级数据DELETEFROMdata_secondWHEREtimestamp<datetime('now','-7 days');-- 清理 30 天前的分钟级数据DELETEFROMdata_minuteWHEREtimestamp<datetime('now','-30 days');-- SQLite 回收空间VACUUM;SQLite 文件超过 300MB 时发出警告,支持手动或配置自动清理策略。
总结
| 能力 | 实现方式 |
|---|---|
| 不怕丢数据 | 双数据库 + 自动降级 |
| 不怕数据量大 | 异步批量写入 |
| 长期数据查得快 | 多级聚合 |
| 标签随意增减 | EAV 模型 |
工业监控的数据存储,不是技术深度的竞赛,而是可靠性的较量。
参考资料
- PLCMonitor 开源项目:https://github.com/freddiezhang1990/plcmonitor
标签:#PLC#工业监控#上位机#MySQL#SQLite#数据库设计