$match阶段必须放在聚合管道最前面,且需包含分片键字段,才能被mongos下推至各分片本地执行;否则将触发全分片广播查询,大幅增加数据传输与内存压力。

分片集群中 $match 阶段必须放在聚合管道最前面
分片集群里,聚合查询的性能瓶颈往往不是计算本身,而是跨分片数据传输。MongoDB 的路由层(mongos)只会在 $match 出现在管道**第一个阶段**时,才尝试将其下推到各分片本地执行。一旦 $match 后面跟着 $project、$addFields 或其他阶段,mongos 就无法安全下推——因为字段可能已被重命名、计算或过滤掉。
- 错误写法:
db.orders.aggregate([{$project:{status:1,amount:1}}, {$match:{status:"completed"}}])→ 所有文档先从各分片拉到 mongos,再过滤,网络和内存压力陡增 - 正确写法:
db.orders.aggregate([{$match:{status:"completed"}}, {$project:{status:1,amount:1}}])→ 各分片独立执行$match,只返回匹配结果 - 复合条件也一样:即使你用
$expr或引用新字段(如"$total"),只要它依赖后续阶段产出,就无法下推
分片键必须参与 $match 过滤条件
即便 $match 在第一位,如果它不包含分片键(shard key)字段,mongos 仍需向**所有分片**广播请求,无法路由到目标分片。这不是慢,是根本性浪费。
- 假设 orders 集合按
{userId: 1, orderId: 1}分片,那么{$match:{userId:"abc123"}}能精准定位 1 个分片;但{$match:{status:"paid"}}会发往全部分片 - 若业务必须按非分片键查询(如按时间范围查订单),应在分片键设计时预留空间:比如把
createdAt加入分片键,或使用哈希分片+二级索引配合 -
explain("executionStats")中检查executionStages.shards.*.executionStages.stage是否为SHARDING_FILTER,而不是全量IXSCAN或COLLSCAN
$lookup 在分片集群中极易成为性能黑洞
分片环境下,$lookup 默认行为是「广播式关联」:主集合在某分片查出一批文档后,mongos 把这些文档的关联字段值收集起来,再分别发给所有分片去查被关联集合——哪怕被关联集合也是分片的,且分片键与关联字段完全一致。
- 避免方式一:确保被关联集合(
from)也按相同字段分片,并在该字段上建索引;然后显式启用let+$expr+pipeline写法,触发「局部关联」 - 避免方式二:用应用层两次查询替代
$lookup,尤其当关联结果集不大时(如查用户信息),反而更可控 - 绝对不要在
$lookup的pipeline里再嵌套$lookup—— 每一层都放大跨分片通信次数,O(n²) 级恶化
allowDiskUse 和 maxTimeMS 在分片环境下的实际影响
这两个选项看似通用,但在分片集群中作用范围不同:allowDiskUse 是每个分片独立生效的,而 maxTimeMS 是 mongos 统一计时的总超时。这意味着:
- 一个分片因磁盘排序卡住,其他分片可能早已返回结果,但 mongos 仍在等它,最终整条管道超时
- 设置
maxTimeMS: 5000并不能保证每个分片最多跑 5 秒,而是所有分片加起来不超过 5 秒 —— 实际单分片可能只分配到 1 秒,尤其当分片数多时 - 真正要控单分片资源,得在分片节点配置
setParameter的maxTimeMS,或靠分片键设计让负载均匀,而非依赖聚合参数
$match,或者一场失控的 $lookup 广播。











