时序集合必须显式创建,不能自动转换;需用createCollection指定timeseries选项,timeField须为Date类型且非空,granularity仅支持seconds/minutes/hours,选错会严重降低压缩率;TTL索引必须复合metaField与timeField;时序集合不支持事务。

时序集合必须显式创建,不能靠插入自动触发
你无法通过往普通集合里插入带 timestamp 字段的文档,就“变成”时序集合。MongoDB 不会根据字段名或内容自动升级——db.sensor.insertOne({ts: ISODate(), temp: 23.5}) 插入的永远是普通文档,不享受压缩、分桶、预聚合等任何优化。
必须用 createCollection 显式声明,且指定 timeseries 选项:
db.createCollection("sensors", {
timeseries: {
timeField: "ts",
metaField: "device",
granularity: "minutes"
}
})
-
timeField必须是Date类型,且所有文档都得有该字段(不能为null或缺失) -
metaField是可选字符串字段,用于存设备 ID、传感器类型等不变元数据,后续按此分组查询才高效 - 创建后无法修改
timeField、metaField或granularity,也不能把已有普通集合转为时序集合
granularity 选错会直接拉低压缩率和查询性能
granularity 不是“精度开关”,而是告诉 MongoDB “同一物理 bucket 里的时间戳大概差多少”。它只有三个合法值:seconds、minutes、hours。选错会导致 bucket 内部稀疏,压缩失效。
- IoT 场景下,若传感器每 30 秒上报一次,却设
granularity: "seconds",MongoDB 会强行按秒切片,产生大量空隙,实测压缩率反而比minutes低 3–5 倍 - 设
minutes更适合温湿度、电表读数等 ≥10 秒间隔、常按小时/天聚合的场景 - 设
seconds仅当业务真需要亚秒级窗口聚合(如高频金融 tick),且能接受更高磁盘开销
注意:它不影响你存什么时间值,也不改变查询写法——$dateTrunc 聚合仍照常工作,但底层组织方式变了。
TTL 索引必须建在 metaField + timeField 复合字段上
时序集合自带自动过期能力,但传统单字段 TTL 索引(如只在 ts 上设 expireAfterSeconds)效率极低:MongoDB 会扫描整条时间线,无法按设备粒度清理。
正确做法是建复合 TTL 索引:
db.sensors.createIndex(
{ device: 1, ts: 1 },
{ expireAfterSeconds: 2592000 } // 30 天
)
- 这样清理时能按
device分批推进,降低单次操作压力 - 如果补录迟到数据(比如往回写 2026-04-01 的记录),这个索引可能连同旧数据一起删掉——因为 TTL 判定只看
ts值,不区分写入时间 - 不要依赖
timeField单独建 TTL;也别漏掉metaField,否则等于放弃时序集合的分片优势
别在事务里混用时序集合和普通集合
Time Series 集合禁用事务是硬性限制,调用 session.startTransaction() 会直接报错 CommandNotSupportedOnTimeSeries。这不是配置问题,而是底层 bucket 压缩模型与 ACID 冲突导致的架构取舍。
- 事务要求对单个测量值精确加锁、回滚,但时序集合把几十条数据打包进一个 bucket 文档,无法做行级控制
- 如果你既要更新设备元数据(如状态、告警计数器),又要写入传感器数据,必须拆成两步:先用事务写普通集合(如
devices),再单独写sensors时序集合 - 写时序失败时,靠应用层幂等控制(例如用
{ deviceId: "D1001", ts: ISODate(...) }建唯一索引)防重复
真正需要高频更新、跨时间点事务联动的场景,往往说明数据建模本身就有偏差:时序数据不该承载状态变更逻辑。


















