如何使用MongoDB聚合API编写查询解决$nin操作慢的问题
性能问题根因
你遇到的$nin性能差是MongoDB多键索引的固有特性导致的:
- 你创建的
(a,1), (b,1), (c,1)是多键索引(因为c是数组类型),$in可以直接通过索引定位到c等于目标值的条目,不需要额外回表过滤; - 但
$nin的逻辑是排除匹配值,索引扫描只能先扫出所有符合a、b条件的索引条目,再把所有对应文档拉回内存逐一校验c数组是否真的不包含排除值,你的执行计划也验证了这点:索引扫描仅耗时140ms,剩下的几乎所有时间都花在FETCH阶段拉取8万+文档、过滤c字段的逻辑上。
需求对应的聚合查询写法
你要的先匹配a、b再过滤c的聚合语句如下,执行效率和你直接查a+b的语句接近:
test_db.aggregate([ # 第一阶段:匹配a、b条件,完全命中现有索引的(a,b)前缀,无额外开销 { '$match': { 'a': a, 'b': b } }, # 第二阶段:在匹配a、b的小结果集里过滤c的$nin条件 { '$match': { 'c': {'$nin': ['some_c']} } }, # 可选:和你原有查询的投影规则保持一致,按需保留 { '$project': { '_id': 0, 'feed_id': 0, 'original_url': 0 } } ])
可选优化方案
如果要进一步提升性能,可以参考两种方案:
- 方案1:新增
(a,1), (b,1)的普通非多键索引,第一阶段匹配a、b时不会触发多键索引的重复条目去重逻辑,索引扫描速度更快 - 方案2:如果
$nin排除的固定值不多,可以新增布尔类型的标记字段,比如has_some_c,写入数据时同步更新该字段值,再将索引调整为(a,1), (b,1), (has_some_c, 1),查询时直接匹配has_some_c: false即可,完全避免$nin的过滤开销
内容的提问来源于stack exchange,提问作者Ahasanul Haque
相关产品推荐
相关产品推荐

