确认是否scatter-gather:用explain("executionstats")检查nshards>1且本意查单分片,或executionstages.shards中多个分片executiontimemillisestimate非零;聚合触发广播主因是$match不满足分片键完整前缀、含不支持路由操作符或分片键类型不一致。

聚合查询在分片集群下变慢,大概率不是聚合本身的问题,而是查询被广播到了所有分片——也就是发生了 Scatter-Gather。它会让总耗时卡在最慢那个分片上,而不是均摊。
怎么确认是不是 Scatter-Gather 导致的慢?
别看 mongos 日志里的总耗时,那包含网络、协调、合并开销,失真严重。必须用 explain("executionStats") 看真实执行路径:
-
nShards字段大于 1,且你本意是查单分片 → 基本坐实广播 -
executionStages.shards列表里多个分片的executionTimeMillisEstimate都非零 → 确实被发往多个分片执行了 - 注意:如果只看到
executionTimeMillis(顶层总时间),没展开shards细节,等于白查
聚合查询为什么特别容易触发广播?
聚合比普通 find 更难路由,$match 必须同时满足三个硬性条件才能走 Targeted Query:
- 必须放在管道最外层(不能嵌套在
$or、$facet或$lookup后面) - 条件中必须包含分片键的完整前缀(比如分片键是
{region: 1, userId: 1},就不能只写{userId: "u123"}) - 不能使用不支持路由的操作符:
$ne、$not、$regex(非^开头)、$where、$text
一旦违反任一条件,mongos 就放弃推导目标分片,直接广播。
分片键类型不一致也会让路由失效
这是最容易被忽略的隐性坑:分片键字段值类型必须严格统一。例如 region 字段,一部分文档存 "cn"(string),另一部分存 ObjectId("..."),哈希计算结果完全不同,mongos 路由时会错乱,导致本该去 shard0 的请求发去了 shard1,甚至跨分片重复发送。
- 检查方式:
db.collection.distinct("region", {}, {hint: {_id: 1}})+ 手动观察类型 - 修复成本高:需全量清洗数据,或重建分片集合
- 预防手段:应用层写入前强校验,或用 schema validation 限制字段类型
聚合里用了 $lookup 就一定广播吗?
不一定,但条件苛刻:
- 被联查的集合也必须是分片集合(不能是副本集)
-
localField和foreignField必须能对齐分片逻辑 —— 比如都落在同一个分片键前缀上(orders.region关联users.region) - 如果
$lookup的 from 集合没分片,或字段无法路由,mongos 会退化为广播 + 内存合并,性能断崖式下跌
真正棘手的是:这种广播不会报错,只会默默变慢,且 explain 里 nShards 可能只显示主集合的分片数,掩盖了 $lookup 侧的实际广播行为。











