MongoDB 时序数据存储的最佳实践:不仅仅是创建集合那么简单
开门见山:在 MongoDB 5.0 及以上版本中,处理时序数据,请优先选择官方提供的 timeseries 集合。别再费力自己写分桶逻辑,或者一条数据一个文档往普通集合里塞了。时序集合绝不是“可选项”,它是 MongoDB 为你量身打造的底层优化引擎。不过,想要用好它,必须遵循一套严格的规则,否则效果可能还不如不用。
下面,我们来逐一拆解这些“核心心法”。
第一步:创建时,必须显式声明
别以为在普通集合里插入一万条带 ts 时间戳的文档,MongoDB 就会“聪明地”自动转为时序集合。它没这个能力,也不会自动升级。普通的文档集合,永远无法变成时序集合。
正确的打开方式只有一个:显式调用 createCollection 命令。示例代码如下:
db.createCollection("sensors", {
timeseries: {
timeField: "ts",
metaField: "device",
granularity: "minutes"
}
})
这里面有几个硬性要求,必须牢记:
timeField字段,必须是Date类型,并且每条文档里都必须包含,不能为null,也不能缺失。metaField是可选字段,比如这里的"device"。它的作用是让你能按设备、传感器等维度进行高效分类和查询,是后续很多高级操作的基础。- 一条铁律:一旦创建,
timeField、metaField和granularity这三个参数就再也不能修改了。 - 也别指望能把一个已存在的普通集合“转”成时序集合,没有这个功能。
第二步:granularity 选错了,代价巨大
granularity 不是让你选“我想查多细”,而是告诉 MongoDB 引擎:“嘿,我这个设备的数据点之间的时间间隔,大概在什么量级?” 它只接受三个值:seconds、minutes、hours。
这个参数选错了,问题很严重:数据的压缩效率会大幅降低,至少下降3到5倍。这可不是开玩笑的。
- 场景一:一个温度传感器,每30秒上报一次数据。这时你应该选
minutes,而不是seconds。选seconds会导致每个物理“桶”内部太稀疏,压缩算法就直接失效了。 - 场景二:
minutes是最常用的设置,适合像温湿度、电表读数这类数据,数据间隔通常在10秒以上,而且分析时习惯按小时、天来聚合的场景。 - 场景三:
seconds只在你真的需要亚秒级窗口聚合时才考虑,比如高频金融交易数据。但要明白,这会带来更高的磁盘开销。 - 重点:选错
granularity不会影响你存什么时间值,也不会改变你写查询的方式(比如$dateTrunc这类函数依然照常工作)。受影响的,是MongoDB底层的文档组织方式。你会发现,磁盘空间蹭蹭往上涨,查询速度却不见得快。
第三步:TTL过期,复合索引才算靠谱
如果只想在 ts 字段上建一个单字段的TTL索引来清理过期数据,那效率会很成问题。因为MongoDB会傻乎乎地从头到尾扫描整个时间线,根本没法按设备粒度“定向清除”。
正确的做法是,构建一个 { metaField: 1, timeField: 1 } 的复合索引。例如:
db.sensors.createIndex({ device: 1, ts: 1 }, { expireAfterSeconds: 2592000 }) // 30天
这样做的好处显而易见:过期清理能按 device 分批进行,大大降低了单次操作的压力。更重要的是,就算后续有迟到的数据补录进来(比如补录昨天甚至更早的数据),这个索引依然能精准定位,并触发相应的清理动作。这才是真正的“可持续维护”。
最后,也是最容易被忽略的一点:时序集合不支持事务。
如果你的业务流程依赖于“写入一条传感器数据”和“更新设备状态”这两个操作必须同时成功或同时失败的原子性,那时序集合就无能为力了。你必须在应用层自己做好兜底逻辑,或者,干脆回到普通集合,用自己手写的分桶设计来保证事务性。别等到系统上线了才发现这个坑,那可就真抓瞎了。
