是什么
$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 往生产主表写:它是覆盖语义,一旦管道条件写错,会在一瞬间把线上的业务集合替换成错误结果,而且没有回滚路径。落库目标应是独立的汇总或缓存集合。