大家好,我是数据库小学妹 👋
前段时间接了一个物联网平台的数据库运维,5万台设备,每台每10秒上报一次指标数据。甲方觉得"设备数据而已,MySQL随便存",建了一张表就开始往里灌。第一天跑了约4.3亿行写入,磁盘IO打满但勉强撑住了。第二天表超10亿行,单条INSERT延迟从毫秒升到秒级。第三天凌晨我被监控告警叫醒,数据库CPU持续100%,写入队列堆积,设备端大量数据上报超时,业务侧已经开始丢数据了。三天,MySQL被打到跪下。
那次之后我重新研究了时序数据该用什么库存。踩过的坑不重复踩,今天把时序数据库的入门要点和选型经验梳理清楚,希望能帮同样困惑的人少走弯路少踩坑。
一、什么是时序数据,为什么MySQL扛不住
时序数据有一个非常鲜明的特征:写多读少,写入是持续不断的流,永远只追加新数据不改旧数据。设备每10秒上报温度、湿度、CPU使用率,这些数据一旦写入几乎永远不会被UPDATE。查询模式也很特殊,极少查单条记录,多数是按时间区间做聚合,比如查"过去24小时的平均温度趋势"或"本周CPU峰值出现在哪个时间段"。这种读写模式和MySQL的设计是冲突的。
MySQL是为随机读写优化的。InnoDB的B+树索引在插入新数据时涉及页分裂和平衡,写入一条数据需要更新索引页和数据页,不会因为"数据是按时间顺序来的"就省掉这个开销。而且时序数据写入量大、历史数据很少被DELETE,表会无限膨胀,单表几亿行之后B+树层级变深,查询和写入一起变慢。
还有一个更隐蔽的坑是MySQL的存储空间膨胀速度远高于时序库。InnoDB的行格式加上索引开销,一条时序数据(时间戳+设备ID+指标值)大约占用80到100字节。专业时序库会用列式压缩、差值编码、字典压缩等手段,同样一条数据可能只需要10到15字节。同样是日增4.3亿行,MySQL需要35到43GB,时序库只需要4到6.5GB。存30天数据,差距是1.2TB对比180GB。云盘成本差异巨大。
我把对比整理成表:
| 对比维度 | MySQL (InnoDB) | 时序数据库 |
|---|---|---|
| 单条存储开销 | 80~100字节 | 10~15字节 |
| 日增4.3亿行占用 | 35~43GB | 4~6.5GB |
| 30天总存储 | ~1.2TB | ~180GB |
| 压缩方式 | 页级基础压缩 | Gorilla XOR+差值编码 |
| 索引维护成本 | 每条INSERT更新B+树 | 无,追加写入 |
我事后做的压测对比能直观说明这个问题。用sysbench模拟时序场景:单表10列(时间戳+设备ID+8个指标),纯INSERT,16线程并发写入。MySQL 8.0在初始化时TPS大约12000,表超过5000万行后TPS开始线性下降,到2亿行时TPS掉到4000以下。TimescaleDB在同样场景下,写入TPS从初始的18000到2亿行时稳定在16500,几乎没有衰减。因为TimescaleDB的Hypertable底层按时间分区自动切chunk,每个chunk是一个独立的小表,写入不会随着总数据量增长而变慢。
二、时序数据库的核心设计差异
传统的时序数据库如InfluxDB用的是类LSM-tree的TSM存储引擎,数据先写到内存缓冲区然后批量刷盘形成不可变的块文件。这种设计的优势在于写入只做顺序IO,单机轻松几十万TPS。代价也明显,查询需要合并多个块文件,有些历史查询会变慢,删除和更新需要做墓碑标记,后续通过compaction合并时才能真正释放空间。
存储能省这么多,核心原因是压缩算法。时序数据相邻两次采样的时间戳差值通常不变,用差分编码只存差值就行;相邻两个指标值变化很小,XOR运算后会产生大量前导零,只存有效位。这就是Gorilla压缩的思路,Facebook 2015年论文里提出的,能把16字节的一条数据压到2字节以下,压缩比8:1起步。MySQL的InnoDB最多做页级压缩,省30%到50%,和专用时序库差了几个数量级。
TimescaleDB走的是另一条路线。它基于PostgreSQL,本质上是在PG上做了一层时序抽象。核心是Hypertable,用户建表后TimescaleDB自动按时间维度把数据切分成多个chunk,底层是PG的标准表,查询时自动路由到对应的chunk。这种设计继承了PG完整的SQL能力、ACID事务和生态工具,开发体验和用MySQL差别不大,学习成本低。微批处理和压缩策略也能达到不错的写入性能,但和纯TSM引擎比还是有差距。
这两条技术路线各有适用场景。需要极致写入性能和时序专用查询语法的工业监控场景,InfluxDB等TSM引擎更合适。时序数据和业务数据需要做JOIN分析或者团队本身习惯SQL操作,TimescaleDB的门槛更低。
国内数据库厂商也在做时序场景。KES V8 R6做了时间分区加自适应行列混合存储,数据按时间自动分片,查询时只扫相关时间段,减少不必要的IO开销。SQL兼容MySQL和Oracle两种模式,对传统业务迁移比较友好。信创场景下覆盖面也广,政企客户可以直接复用已有适配。
我实际选型时,团队对PostgreSQL有技术积累,业务也需要把时序数据和设备元数据做关联查询,所以选了TimescaleDB。但这不是唯一正确答案——如果团队已经有KES使用经验,或者项目涉及信创适配要求,KES是同样值得认真考虑的方案。这笔账要算团队能力、适配要求和长期维护成本,光比技术指标没有意义。
三、从MySQL迁移到时序库的过程
迁移本身不复杂,但有几个决策点必须提前想清楚。这几个坑我都踩过,有的还踩了两次。
首先是数据模型的重构。MySQL里的时序表通常是一张大宽表,每个指标一列。时序库更推荐窄表模型,时间戳、设备ID、指标名、指标值四列。
宽表结构大概是这样的:
-- MySQL宽表:每个指标一列
CREATE TABLE device_metrics (
id BIGINT AUTO_INCREMENT,
ts DATETIME,
device_id VARCHAR(32),
temperature DECIMAL(5,2),
humidity DECIMAL(5,2),
cpu_usage DECIMAL(5,2),
memory_usage DECIMAL(5,2),
-- 再加8个指标列...
PRIMARY KEY (id),
INDEX idx_ts (ts)
);
查某个时间点的所有指标很方便,但加新指标需要ALTER TABLE。窄表长这样:
-- 时序库窄表:一行一个指标值
CREATE TABLE sensor_data (
ts DATETIME NOT NULL,
device_id VARCHAR(32) NOT NULL,
metric_name VARCHAR(64) NOT NULL,
value DECIMAL(10,2)
);
窄表加新指标只需INSERT新行,扩展性更好,但查询需要PIVOT处理。我的场景里设备指标类型新增频繁,选了窄表,SQL写起来略繁琐但运维压力小了很多。
其次是历史数据的迁移策略。冷热分离是迁移时的最佳时机,近7天热数据全量迁移,7到30天的温数据只保留小时级聚合,30天以上的冷数据保留天级聚合并压缩归档。等回查冷数据的时候从归档表里拿天级数据,业务可接受。这样迁移后的表体积只有原来的20%,查询反而更快了。
最后是查询方式的调整。MySQL里查"过去24小时温度趋势"用的是GROUP BY date_format,聚合时全表扫描:
-- MySQL写法:全表扫描
SELECT DATE_FORMAT(ts, '%H:00') AS hour,
AVG(temperature) AS avg_temp
FROM device_metrics
WHERE ts >= NOW() - INTERVAL 24 HOUR
GROUP BY hour ORDER BY hour;
时序库用time_bucket函数一行搞定,背后走的是时间分区裁剪,只扫需要的chunk,IO开销非常小:
-- TimescaleDB写法:时间分区裁剪
SELECT time_bucket('1 hour', ts) AS hour,
AVG(value) AS avg_temp
FROM sensor_data
WHERE ts >= NOW() - INTERVAL '24 hours'
AND metric_name = 'temperature'
GROUP BY hour ORDER BY hour;
我在迁移后用Grafana接了TimescaleDB做设备监控面板,过去在MySQL上刷新耗时45秒的曲线,现在0.3秒出图,体感差异非常明显。
四、避坑清单
别以为加了时间索引就万事大吉。我在MySQL优化阶段试过在时间戳列建索引,写入TPS反而降了。因为每INSERT一条数据都要维护B+树索引,自增时间戳导致索引写入集中在最右边的叶子节点,虽然不会随机分裂但依然有成本。索引对时序查询的帮助也有限,WHERE time BETWEEN A AND B走索引范围扫描,但2亿行里扫48小时的数据依然要扫几百万行,索引回表导致随机IO。
压缩策略不可贪心。TimescaleDB的压缩功能很强大,开启后存储体积能缩小10倍以上。但压缩chunk是不可变的,一旦压缩就不能再修改。如果业务里需要对近期数据做修正,压缩策略最好给一个缓冲窗口,比如只压缩7天前的chunk。我一开始设了压缩延迟1天,结果业务侧频繁修正前一天的数据,每次都要解压再重压CPU消耗反而更高了。
时序库不是银弹。时序库解决了写入和时序查询的问题,但复杂的事务性操作如设备注册、配置变更、告警规则更新不建议也扔进时序库处理。我最终的架构是MySQL管设备元数据和告警规则,TimescaleDB管时序指标数据,Kafka做中间管道。有些项目不需要维护两套系统,用KES这种一套库同时支持关系数据和时序数据的方案也能跑通,省掉跨库同步的麻烦。一张库打天下的想法在时序场景里千万别有,但一套融合数据库覆盖多类型数据负载是另一回事。
你在IoT或监控场景用过时序库吗?选的是哪家?踩过什么坑?评论区聊聊。
我是数据库小学妹,咱们下篇见 👋