首页/数据/NoSQL 速查/聚合 $out 覆盖输出集合
MongoDB99 条

聚合 $out 覆盖输出集合

$out 把管道结果写入集合:目标存在时整体替换(原子性切换),不存在则创建;必须作为管道最后阶段。

NoSQL 速查数据更新于 2026-10-13

是什么

$out 是聚合管道的落库阶段:把前面所有阶段算出来的结果写进一个集合。它的语义是「整体替换」——目标集合已存在时,先写临时集合,写完再原子替换;目标不存在则直接创建。因此它天然适合每日重算的汇总表,而不适合增量累加,后者要用 $merge。

基本信息

语法{ $out: "coll" } 或 { $out: { db: "otherDb", coll: "coll" } }
位置要求必须是管道的最后一个阶段,后面不能再跟其他阶段
替换过程先建临时集合、把原集合索引一并复制过去、写入结果,最后整体改名替换
失败行为管道中途报错时不改动目标集合
同族命令$merge(MongoDB 4.2+)支持增量合并与条件写入

典型使用场景

  • 每日汇总表:把明细按天重算成指标表,用 $out 直接替换,不需要先删除再插入
  • 结果缓存:把开销很大的聚合结果物化成集合,报表与接口只查物化结果
  • 数据清洗落库:过滤、重塑、脱敏后写成干净版本,供下游只读使用
  • 跨库整理:把计算结果写到另一个数据库($out 支持指定 db),隔离读写负载
  • 离线实验:把候选算法结果落到单独集合,与线上表对比后再决定是否切换

重算日报表

重算日报表
db.orders.aggregate([
  { $match: { status: "paid", createdAt: { $gte: ISODate("2026-10-01") } } },  # 先缩小输入
  { $group: { _id: { sku: "$sku", day: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } } }, total: { $sum: "$qty" } } },
  { $sort: { "_id.day": 1 } },                    # 让结果有序,便于下游直接扫表
  { $out: "sku_daily" }                           # 必须放在最后:整体替换目标集合
], { allowDiskUse: true })                        # 数据量大时允许中间结果落盘

db.sku_daily.getIndexes()                         # 核对原集合的索引是否已随替换保留
db.sku_daily.countDocuments({})                   # 校验结果行数是否符合预期

$out 与 $merge 差异

维度$out$merge
已有数据整体替换,旧数据不再保留按条件合并,可保留历史
唯一索引要求不要求用 on 字段时目标集合需要有对应唯一索引
适用场景可重算的汇总表、快照增量累加、幂等重跑、变更回写
幂等性重跑即重算,结果一致重跑要保证累加逻辑不会重复计入
$out 的替换发生在整个管道成功之后,中途报错不会把目标表写坏;替换时原集合的索引会被复制到新集合,索引依赖的查询不受影响,但前提是目标集合原本就存在且有索引。
不要用 $out 往生产主表写:它是覆盖语义,一旦管道条件写错,会在一瞬间把线上的业务集合替换成错误结果,而且没有回滚路径。落库目标应是独立的汇总或缓存集合。

易错点

  • $out 后面再写其他阶段会直接报语法错误,管道设计时要先想清楚输出位置
  • 把 $out 当成增量更新的手段:每次运行都是整体替换,配合按天累加的逻辑会丢掉历史数据
  • 对同一个目标集合并发跑多个 $out 聚合:互相覆盖,最终结果取决于谁后完成,必须靠调度或加锁避免叠加运行
  • 在分片集群上向已分片集合输出时未考虑分片键,写入可能失败或退化;跨版本行为有差异,动手前查对应版本手册

数据来源与参考

  • MongoDB Manual: $out
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌