MongoDB特定搜索查询响应过慢的排查与优化咨询
MongoDB查询性能排查与优化问题
案例描述
- 基于Spring Initializr生成的简单应用,连接MongoDB。
- 应用需执行count和find两个查询并返回结果(Mongo查询来自MongoTemplate的debug日志,因此显示
$regularExpression而非$regex)。
查询语句:
{ "$and": [ {"domain": "test"}, {"deleted": {"$ne": true}}, {"specialItem": {"$ne": true}}, {"$or": [ {"textSearch": {"$regularExpression": {"pattern": "\\QHANDLE\\E", "options": ""}}} ] }, { "$or": [ {"textSearch": {"$regularExpression": {"pattern": "\\QMOUNT\\E", "options": ""}}} ] } ] }
生成该查询的代码:
@GetMapping("/demo") public ResponseEntity<String> demo() { final List<org.springframework.data.mongodb.core.query.Criteria> criterias = new ArrayList<>(); criterias.add(Criteria.where("domain").is("test")); criterias.add(Criteria.where("deleted").ne(true)); criterias.add(Criteria.where("specialItem").ne(true)); for (String word : List.of("handle", "mount")) { criterias.add(new Criteria().orOperator( Criteria.where("textSearch").regex("^.*" + Pattern.quote(word.toUpperCase()) + ".*") )); } final List<Criteria> orCriterias = new ArrayList<>(); if(!orCriterias.isEmpty()) criterias.add(new Criteria().orOperator(orCriterias)); Query q = Query.query(new Criteria().andOperator(criterias.toArray(new Criteria[]{}))).with(Sort.by("domain", "deleted", "itemNumber")); q = q.skip(0).limit(20); long total = mongoOperations.count(q, DaoItem.class); final List<DaoItem> results = new ArrayList<>(); List<DaoItem> daoItems = mongoOperations.find(q, DaoItem.class); results.addAll(daoItems); return ResponseEntity.ok("total: " + total + ", result: " + results); }
textSearch是字符串数组,示例:["00000001", "SMOUNT.", "TER.", "YYY", "1", "GLASS"]
问题描述
上述查询在不同运行环境中响应时间差异显著,Dev环境耗时过长。
环境说明
mongo-java-driver|sync版本:4.7.1- MongoDB版本各环境略有不同(主版本一致)
spring-boot版本:3.1.3
使用JMeter循环测试各环境响应时间,测试均针对test域,非本地环境ping服务器耗时约160ms。
各环境详情:
- 本地环境
- DB:Docker部署的MongoDB(v3.6.19),含747259条文档
- 资源:M1 Max + 64GB内存
- 存储空间:2TB总容量,剩余530GB
- 响应时间:约200-300ms
- Dev环境
- DB:物理机部署的MongoDB(v3.6.8),含747259条文档
- 资源:Intel(R) Xeon(R) CPU E5-2660 0 @ 2.20GHz + 5.78GB内存
- 存储空间:/dev/sda1 146G,已用9.3G,剩余129G
- 响应时间:3-8s
- QA环境
- DB:物理机部署的MongoDB(v3.6.8),含42491条文档
- 资源:Intel(R) Xeon(R) CPU E5-2660 0 @ 2.20GHz + 5.78GB内存
- 存储空间:/dev/sda1 146G,已用6.3G,剩余132G
- 响应时间:约2-3s
观察结果
- 在远程服务器部署全新Docker版MongoDB并导入Dev环境数据后,响应时间与Dev环境相近
- 因使用部分匹配查询,无法利用索引,执行计划为不含textSearch的IXSCAN
- 业务需部分匹配,因此无法使用MongoDB的text索引
- 各环境MongoDB配置均为默认
- Dev环境查询explain结果:
Documents Returned: 138 Index Keys Examined: 62448 Documents Examined: 62446 Actual Query Execution Time (ms): 2479 Sorted in Memory: no Query used the following index: DOMAIN, DELETED SPECIALITEM
调整代码生成多种查询语句后,响应时间无明显变化,例如:
{ "$and": [ {"domain": "test"}, {"deleted": {"$ne": true}}, {"specialItem": {"$ne": true}}, {"$or": [ {"searchWords": {"$regularExpression": {"pattern": "\\QHANDLE\\E","options": ""}}}, {"searchWords": {"$regularExpression": {"pattern": "\\QMOUNT\\E","options": ""}}} ] } ] }
{ "$and": [ {"domain": "test"}, {"deleted": {"$ne": true}}, {"specialItem": {"$ne": true}}, {"searchWords": {"$regularExpression": {"pattern": "\\QHANDLE\\E","options": ""}}}, {"searchWords": {"$regularExpression": {"pattern": "\\QMOUNT\\E","options": ""}}} ] }
另一个查询的explain结果(精简版):
查询语句:
{ "domain": "test", "deleted": {"$ne": true}, "specialItem": {"$ne": true}, "searchWords": {"$regex": "(HANDLE|MOUNT)"} }
explain结果:
Documents Returned: 2853 Index Keys Examined: 76606 Documents Examined: 76606 Actual Query Execution Time (ms): 1391 Sorted in Memory: no Query used the following index: DOMAIN ITEMNO --- FETCH nReturned: 2853 Execution Time: 974ms IXSCAN nReturned: 76606 Execution Time: 358ms Index Name: domain_-1_itemNo_-1 Multi Key Index: no
疑问
作为MongoDB初学者,已无排查思路,想咨询:
- 针对该查询,还有哪些方向可排查Dev环境响应慢的原因?
- 在MongoDB层面,可采取哪些优化手段将Dev环境的响应时间降至约2秒?(暂不考虑迁移至Elasticsearch)
解答
一、Dev环境响应慢的排查方向
- 资源瓶颈分析
- 用
top/htop查看MongoDB进程的CPU、内存占用,确认是否存在CPU性能瓶颈(E5-2660单核心性能远低于M1 Max)或内存不足导致的频繁磁盘IO。 - 用
iostat/iotop检查磁盘读写速率、等待时间,确认物理机磁盘是否为机械盘(对比本地SSD的IO性能差异)。
- 用
- MongoDB内部状态检查
- 执行
db.serverStatus(),重点关注cache(缓存命中率)、metrics.query.executor(查询执行统计)、storageEngine(存储引擎状态)。缓存命中率低说明内存不足,大量数据需从磁盘读取。 - 开启慢查询日志(设置
slowms=1000),记录慢查询的详细执行信息,对比不同环境的日志差异。
- 执行
- 数据与索引有效性验证
- 检查
domain:test的数据占比,若该域数据量过大,会导致索引扫描范围扩大。 - 执行
db.collection.getIndexes()查看所有索引,结合explain("executionStats")确认索引是否被正确使用;用db.collection.reIndex()重建索引,观察性能变化。
- 检查
- 版本差异验证
- 本地用MongoDB 3.6.19,Dev用3.6.8,小版本可能包含性能修复。尝试将Dev环境MongoDB升级到3.6.19,测试响应时间是否改善。
二、MongoDB层面的优化手段
- 优化查询逻辑
- 将多个独立
$or合并为一个,减少查询层级:{ "$and": [ {"domain": "test"}, {"deleted": {"$ne": true}}, {"specialItem": {"$ne": true}}, {"$or": [ {"textSearch": {"$regularExpression": {"pattern": "\\QHANDLE\\E", "options": ""}}}, {"textSearch": {"$regularExpression": {"pattern": "\\QMOUNT\\E", "options": ""}}} ] } ] } - 避免前后都带通配符的正则(
^.*xxx.*$),若业务允许,调整为后缀匹配(xxx.*)以尝试利用前缀索引。
- 将多个独立
- 调整索引策略
- 优化现有索引字段顺序,尝试创建
{domain:1, deleted:1, specialItem:1, textSearch:1}的多键索引,减少FETCH阶段的文档扫描量。 - 使用覆盖索引:若查询仅需返回特定字段,创建包含目标字段的索引(如
{domain:1, deleted:1, specialItem:1, textSearch:1, itemNumber:1}),避免读取完整文档。
- 优化现有索引字段顺序,尝试创建
- 调整MongoDB配置
- 调高WiredTiger缓存:修改
mongod.conf中storage.wiredTiger.engineConfig.cacheSizeGB参数(比如设为4GB,不超过系统可用内存的80%),减少磁盘IO。 - 设置查询批处理大小:在Java代码中为查询添加
q.batchSize(50),避免一次性读取过多数据。
- 调高WiredTiger缓存:修改
- 数据预处理
- 将
textSearch数组的元素合并为单个字符串字段(如textSearchKeywords),用空格分隔,减少MongoDB对数组的遍历次数。 - 若业务允许,提前将高频查询关键词转为布尔字段(如
hasHandle、hasMount),直接匹配布尔字段替代正则查询。
- 将
内容的提问来源于stack exchange,提问作者user3529850
相关产品推荐
相关产品推荐

