时间分桶聚合必须用 $datetrunc,不能用 $group 手动算时间戳;$out 写入需显式声明 timeseries 配置;首个 $match 必须仅含 timefield 范围条件以命中索引;$merge 增量写入时须保留 timefield 字段并校验结构。

时间分桶聚合必须用 $dateTrunc,不能用 $group 手动算时间戳
在 MongoDB 5.0+ 的时间序列集合(timeseries)上做分桶(如按小时、天聚合),核心是利用 $dateTrunc 操作符对 timeField 做截断。手动用 $add/$multiply 计算时间戳再 $group 不仅易错,还会绕过时间序列引擎的优化路径,导致无法使用底层的时间分桶索引加速。
常见错误现象:聚合结果为空或性能极差,explain() 显示全表扫描(COLLSCAN)而非时间范围索引扫描(IXSCAN)。
-
$dateTrunc是唯一被时间序列引擎识别并用于下推优化的分桶方式 - 必须作用于原始
timeField字段(如"ts"),不能作用于计算出的新字段 - 粒度(
unit)必须与建表时指定的granularity兼容:若建表为"minutes",则unit: "hour"合法,但unit: "second"会触发警告且不加速
副本集环境下 $out 写入时间序列集合需显式指定 timeseries 配置
直接用 { $out: "target_ts_coll" } 会失败——即使目标集合已存在且是时间序列类型,MongoDB 也不会自动复用其 timeseries 元数据。必须在 $out 阶段中完整声明 timeseries 结构,否则写入将创建普通集合,丢失时间序列语义。
错误信息示例:"errmsg" : "Cannot write to a time-series collection without specifying timeseries options"
- 正确写法:
{ $out: { db: "mydb", coll: "hourly_metrics", timeseries: { timeField: "ts", metaField: "device_id", granularity: "hours" } } } -
granularity必须与目标集合实际配置一致;若不匹配(如目标为"minutes"却写"hours"),操作会失败 - 副本集主节点执行该管道即可,从节点不参与写入;但确保所有节点的
featureCompatibilityVersion≥ 5.0,否则$out中的timeseries字段会被忽略
聚合前必须确保查询条件能命中时间范围索引
时间序列集合默认在 timeField 上建有特殊索引,但该索引只对形如 { ts: { $gte: ..., $lt: ... } } 的范围查询生效。若聚合开头是 $match 但用了 $eq、$in 或非时间字段过滤,整个管道可能退化为全量扫描。
典型场景:想查某设备某天的数据,却写成 { device_id: "d123", ts: { $gte: ISODate("..."), $lt: ISODate("...") } } —— 这里 device_id 是 metaField,必须配合 ts 范围才能触发复合索引。
- 最佳实践:第一个
$match阶段只包含timeField的范围条件,后续再加metaField过滤 - 避免在
$match中使用$expr或聚合表达式,它们无法下推到索引层 - 用
db.runCommand({ explain: { aggregate: ... } })确认executionStats.executionStages.stage是IXSCAN而非COLLSCAN
$merge 替代 $out 时需注意时间序列字段校验
若目标时间序列集合已存在且需增量写入(如追加每小时聚合结果),必须用 $merge 而非 $out。但 $merge 会严格校验文档结构:写入文档必须包含 timeField 字段,且值必须是有效 BSON 日期;若聚合结果中该字段被重命名或缺失,操作直接失败。
错误信息示例:"errmsg" : "time field 'ts' is missing from document"
- 确保聚合最后阶段输出的文档保留原始
timeField名称(如ts),不要用$project改名 - 若需补全
metaField(如device_id),必须在$merge前通过$addFields显式注入,不能依赖目标集合默认值 -
$merge的on字段只能是_id或timeField+metaField组合,不能用其他字段做合并键
timeField 和建表时的 granularity 上,任何脱离这个链条的操作(比如手动构造时间戳、跳过 $dateTrunc、忽略 timeseries 配置)都会让聚合失去原生加速能力,最终退化成普通集合处理。











