$out 会直接覆盖目标集合,因为其本质是原子性重命名:先写入临时集合,再用 renameCollection 替换原集合,导致原数据、索引、TTL 和分片配置全部丢失,且不支持增量操作。

$out 会直接覆盖目标集合,没有确认、没有回滚、没有增量 —— 这是它的设计行为,不是 bug。
为什么 $out 总是清空再写入
MongoDB 的 $out 阶段本质是「原子性重命名」:先在后台创建一个临时集合(如 esi.tmp.agg_out.2),把聚合结果写进去,最后一步用 renameCollection 把临时集合直接重命名为目标集合名。整个过程不保留原数据,也不做文档级比对。
这意味着:
- 即使只改了管道里一个
$project字段,$out也会抹掉目标集合全部旧文档 - 目标集合的索引、TTL 设置、分片键配置等全部丢失,新集合只保留默认索引(
_id) - 如果目标集合正在被其他应用读取,重命名瞬间会出现短暂不可读(虽快但非零延迟)
$out 必须放在管道末尾,且不能和 $merge 混用
$out 是聚合管道的终止符 —— 它之后不能再接任何阶段,否则会报错 "$out must be the last stage in the pipeline"。
常见误用场景:
- 在
$out后面加$limit或$sort→ 直接失败 - 试图用
db.collection.aggregate([...], {$out: "coll"})这种 shell 语法(错误格式)→ 实际应写成[..., {$out: "coll"}]数组内嵌 - 在事务中使用
$out→ MongoDB 明确禁止,会抛出"$out is not allowed in transactions"
跨数据库输出需显式指定 { db: "...", coll: "..." }
MongoDB 4.4+ 支持跨库写入,但必须用对象语法,不能只传字符串:
db.source.aggregate([
{ $match: { status: "active" } },
{ $out: { db: "analytics", coll: "user_summary" } }
])
注意点:
- 单机或副本集:若
analytics库不存在,$out会自动创建 - 分片集群:
db字段指定的库必须已存在,否则报错"output database must already exist" - 权限要求:需要对目标库有
insert+dropCollection权限(因涉及重命名)
想追加或合并?别用 $out,改用 $merge
如果你的场景是“每次聚合后新增一批文档”或“按 _id 更新已有记录”,$out 就是错选 —— 它天生不支持追加。
替代方案只有 $merge:
-
$merge允许你指定on: "_id",对已存在文档执行replace、keepExisting或自定义whenMatched更新逻辑 - 能输出到分片集合,也支持写入源集合本身(
$merge可以写回source,$out不行) - 语法更啰嗦,但语义清晰:
{ $merge: { into: "target", on: "_id", whenMatched: "replace", whenNotMatched: "insert" } }
真正容易被忽略的是:很多人在 shell 里试完 $out 觉得“快又简单”,上线后才发现业务依赖的是增量更新 —— 这时候再切到 $merge,往往要重写整个调度逻辑和幂等校验。











