$out 不支持直接写入分片集合,因其需原子性 drop+重建,而分片集群无法安全执行;必须先写入非分片集合再手动分片,或改用 $merge 实现增量写入。

聚合结果写入新分片集合时,$out 为什么报错 “not supported on sharded collections”?
直接对分片集合执行 $out 到另一个分片集合会失败,错误信息是 not supported on sharded collections。这不是权限或语法问题,而是 MongoDB 的硬性限制:在分片集群中,$out 目标集合**必须是非分片的(即存在于主分片上)**,否则操作被拒绝。
根本原因是 $out 内部会先 drop 目标集合再重建,而分片集合的元数据管理依赖 config server 和 shards 的协同,无法安全原子地完成跨分片的 drop + reshard 操作。
- 目标集合若已存在且是分片的 → 必报错,无论是否空
- 目标集合不存在 → MongoDB 会自动创建,但默认创建为非分片集合(位于 primary shard)
- 想让输出集合也是分片的?必须分两步:先
$out到非分片集合,再用sh.shardCollection()手动分片
如何让 $out 输出到带分片键的新集合并保持高效?
不能一步到位,但可以控制输出结构和后续分片策略。关键在于:输出集合的文档结构必须包含你计划用作分片键的字段,且该字段在 $out 前的 pipeline 中已计算好、非空、类型稳定。
- 示例场景:按
region和date两字段聚合,希望最终集合以{region: 1, date: 1}分片 - 聚合 pipeline 开头需确保
region和date字段存在且可索引,比如:{$addFields: {region: {$ifNull: ["$region", "unknown"]}, date: {$dateToString: {format: "%Y-%m-%d", date: "$timestamp"}}}} -
$out指向一个新集合名(如"aggr_sales_by_region_date"),它将自动建为非分片集合 - 聚合完成后,立即执行:
sh.shardCollection("db.aggr_sales_by_region_date", {"region": 1, "date": 1})
注意:如果输出文档中 region 或 date 大量为 null 或类型混杂(如 string 和 date 交替),shardCollection 会失败或导致 chunk 分布严重不均。
$merge 能替代 $out 实现分片目标吗?
可以,而且更灵活,但代价是性能和语义差异。$merge 支持写入已分片的目标集合,只要目标集合的分片键与 pipeline 输出文档中的对应字段匹配即可。
- 必须显式指定
on字段(即分片键字段),且这些字段必须在 pipeline 输出中存在 - 不自动 drop 原集合,而是按
on字段做 upsert —— 这意味着你要自己处理重复逻辑或清空旧数据 - 性能通常比
$out低,尤其当目标集合大、匹配键多时,因为要逐文档查、更新或插入 - 典型用法:
{$merge: {into: "aggr_sales_sharded", on: ["region", "date"], whenMatched: "replace", whenNotMatched: "insert"}}
如果你需要增量更新或避免全量重建,$merge 是唯一可行方案;如果只是定时全量重算,$out + 后续 shardCollection 更快、更可控。
分片键选择不当会导致聚合结果写入后查询变慢甚至失败
很多人只关注聚合逻辑,却忽略输出集合的分片键设计。一旦 shardCollection 执行成功,分片键就不可更改 —— 错了就得导出重建。
- 避免单值或低基数字段(如
status: "done")作为分片键前缀,会导致所有数据落到一个 chunk - 时间字段单独作分片键(如
{date: 1})易产生“热点”,新数据持续写入同一 shard - 推荐组合:高频查询维度 + 时间(如
{region: 1, date: 1}),既支持按 region 查,也支持按 region+date 范围查,chunk 分布相对均衡 - 验证方法:聚合后,在 mongos 上运行
sh.status(),检查目标集合的 chunks 是否均匀分布在各 shard 上
真正容易被忽略的是:$out 输出的文档结构决定了你能选什么分片键,而这个结构早在 pipeline 的 $group 或 $project 阶段就固定了 —— 后面没法补。


















