$match放后面会导致全量数据跨节点传输,因为主节点需先拉取整个集合(或大部分)再过滤,浪费带宽与内存;若$match字段无索引、位置不当、类型不匹配或关联集合缺索引,则必然触发全量扫描。

为什么$match放后面会导致全量数据跨节点传输
副本集里聚合默认在主节点执行,但一旦管道里有$lookup、$unwind或$group,MongoDB 可能触发分片式调度逻辑——即使你没分片,也会模拟“把数据拉到一处再处理”。$match放后面时,主节点得先把整个集合(或大部分)从从节点同步过来,再过滤,白白消耗网络带宽和内存。
常见错误现象:totalDocsExamined接近集合总文档数,executionTimeMillis高且波动大,监控看到主从间流量突增。
- 只要
$match字段没建索引,或者放在$lookup之后,就大概率触发全量拉取 - 复合索引字段顺序不匹配(比如索引是
{status: 1, createdAt: 1},但$match写成{createdAt: {$gte: ...}, status: "done"}),索引失效,照样全量扫 - 用字符串查
ObjectId字段(如{_id: "654321..."}),类型不一致,索引跳过,等同于没索引
explain("executionStats")里哪些字段暴露了这个问题
跑一遍 db.collection.aggregate(pipeline, {explain: "executionStats"}),重点盯三个值:
-
totalDocsExamined远大于你预期的过滤后数量(比如查 100 条却扫了 50 万)→ 索引没生效或$match太晚 -
nReturned很小但totalKeysExamined也低 → 索引根本没被选中,不是慢在阶段,是压根没走索引路径 - 任意 stage 下出现
"stage": "COLLSCAN"→ 特别是$lookup的from集合里有这个,说明关联表缺索引,主集合$match再早也没用
注意:executionStats 的 allPlansExecution 里如果显示多个 plan,但只用了 COLLSCAN,基本可断定索引设计或查询写法有问题。
为什么$match前置不一定能解决副本集性能问题
前置只是必要条件,不是充分条件。副本集场景下还有几个隐藏坑:
-
$lookup的from集合没在从节点建索引 → 主节点做完$match后,关联时仍要全量扫描从节点的集合 - 用了
$expr或$or包含非索引字段 → 整个$match条件放弃索引,前置也白搭 - 查询带
readPreference: "secondary"却没在从节点同步索引 → 从节点执行聚合时连索引都没有,必然 COLLSCAN - 副本集版本低于 4.2 → 某些优化(如 pipeline 内部 $match 下推)不支持,前置效果打折扣
验证方式:在从节点上手动运行 db.otherCollection.getIndexes(),确认关联字段索引存在且结构一致。
allowDiskUse:true 在副本集里不只是防报错
默认 100MB 内存限制在副本集里更危险:一旦$group或$sort超限,主节点可能降级到磁盘排序,但数据还在从节点上,跨节点落盘 I/O 放大,延迟飙升甚至超时。
- 必须显式传
{allowDiskUse: true},不能依赖静默行为 - 但开了之后要配合
explain看是否真触发了"memUsage"超限 —— 如果没超限却开,反而增加磁盘调度开销 - 生产环境建议所有含
$group、$sort、$unwind的聚合都加,哪怕当前数据量小,防止某天突然增长崩掉
真正难调的,是那些$match看似前置、索引也建了,但因为副本集同步延迟或读偏好配置错,导致实际执行时根本没走索引——这种问题不会报错,只会慢慢变慢,排查时容易绕远路。











