MongoTemplate查询数组字段完全匹配指定值列表的性能与结果异常问题
问题根因
- 聚合阶段顺序不合理:当前逻辑是先全量执行
$lookup关联、$unwind拆分数组后才执行$match过滤,会先生成大量无效中间数据,放大查询开销,不管用什么查询条件都会变慢 - 缺少对应索引:数组字段的
$all查询如果没有索引会触发全表扫描,耗时自然远高于走了索引的$in查询 - 多余的查询条件:
$all本身就包含「包含所有传入值」的逻辑,额外加$in属于冗余条件,不会优化性能反而会增加查询判断开销 - 分页逻辑顺序错误:
$skip、$limit放在$sort前,会导致分页结果不符合预期
优化方案
1. 调整聚合阶段顺序,提前过滤+关联带条件
MongoDB 3.6及以上版本支持$lookup带过滤条件的关联写法,直接在关联阶段就过滤掉不符合tags要求的子文档,减少中间数据量,同时主表的title过滤放到最前置,先过滤主表再关联:
public Page<MyDocument> findByProperties(String title, List<ObjectId> tags, Pageable page) { final List<Criteria> masterCriteria = new ArrayList<>(); // 主表条件先过滤,缩小关联基数 if (title != null && !title.isEmpty()) { masterCriteria.add(Criteria.where("title").is(title)); } // 构造lookup的pipeline,直接在关联时过滤tags List<AggregationOperation> lookupPipeline = new ArrayList<>(); if (tags != null && !tags.isEmpty()) { lookupPipeline.add(Aggregation.match(Criteria.where("tags").all(tags))); } Aggregation aggregation; if (!masterCriteria.isEmpty() || !lookupPipeline.isEmpty()) { aggregation = Aggregation.newAggregation( // 第一步:先过滤主表,减少后续关联的数据量 Aggregation.match(new Criteria().andOperator(masterCriteria.toArray(new Criteria[0]))), // 带条件的lookup,只关联符合tags要求的子文档 LookupOperation.newLookup() .from("from_collection") .localField("_id") .foreignField("idParent") .pipeline(lookupPipeline) .as("MyBook"), Aggregation.unwind("MyBook"), // 排序放在分页前面 Aggregation.sort(page.getSort()), Aggregation.skip(page.getOffset()), Aggregation.limit(page.getPageSize()) ); } else { aggregation = Aggregation.newAggregation( Aggregation.lookup("from_collection", "_id", "idParent", "MyBook"), Aggregation.unwind("MyBook"), Aggregation.sort(page.getSort()), Aggregation.skip(page.getOffset()), Aggregation.limit(page.getPageSize()) ); } List<MyDocument> results = mongoTemplate.aggregate(aggregation, "my_document", MyDocument.class).getMappedResults(); // 分页总数逻辑优化:获取准确总条数,不直接用返回结果的size long total = mongoTemplate.aggregate(Aggregation.newAggregation( Aggregation.match(new Criteria().andOperator(masterCriteria.toArray(new Criteria[0]))), LookupOperation.newLookup() .from("from_collection") .localField("_id") .foreignField("idParent") .pipeline(lookupPipeline) .as("MyBook"), Aggregation.unwind("MyBook"), Aggregation.count().as("total") ), "my_document", Map.class) .getUniqueMappedResult() .getOrDefault("total", 0L); return PageableExecutionUtils.getPage(results, page, () -> total); }
2. 新增索引覆盖查询条件
在MongoDB控制台执行以下命令创建索引,避免全表扫描:
# 主表title查询索引 db.my_document.createIndex({title: 1}) # 关联表覆盖关联字段+tags过滤的复合索引,同时支持关联查询和tags的$all查询 db.from_collection.createIndex({idParent: 1, tags: 1})
3. 删除冗余查询条件
去掉in(tags).all(tags)的冗余写法,直接保留all(tags)即可,$all本身就可以满足「包含所有传入tags」的查询要求,返回结果和你控制台直接执行的效果一致。
内容的提问来源于stack exchange,提问作者Barbi
相关产品推荐
相关产品推荐

