You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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初学者,已无排查思路,想咨询:

  1. 针对该查询,还有哪些方向可排查Dev环境响应慢的原因?
  2. 在MongoDB层面,可采取哪些优化手段将Dev环境的响应时间降至约2秒?(暂不考虑迁移至Elasticsearch)

解答

一、Dev环境响应慢的排查方向

  1. 资源瓶颈分析
    • 用top/htop查看MongoDB进程的CPU、内存占用,确认是否存在CPU性能瓶颈(E5-2660单核心性能远低于M1 Max)或内存不足导致的频繁磁盘IO。
    • 用iostat/iotop检查磁盘读写速率、等待时间,确认物理机磁盘是否为机械盘(对比本地SSD的IO性能差异)。
  2. MongoDB内部状态检查
    • 执行db.serverStatus(),重点关注cache(缓存命中率)、metrics.query.executor(查询执行统计)、storageEngine(存储引擎状态)。缓存命中率低说明内存不足,大量数据需从磁盘读取。
    • 开启慢查询日志(设置slowms=1000),记录慢查询的详细执行信息,对比不同环境的日志差异。
  3. 数据与索引有效性验证
    • 检查domain:test的数据占比,若该域数据量过大,会导致索引扫描范围扩大。
    • 执行db.collection.getIndexes()查看所有索引,结合explain("executionStats")确认索引是否被正确使用;用db.collection.reIndex()重建索引,观察性能变化。
  4. 版本差异验证
    • 本地用MongoDB 3.6.19,Dev用3.6.8,小版本可能包含性能修复。尝试将Dev环境MongoDB升级到3.6.19,测试响应时间是否改善。

二、MongoDB层面的优化手段

  1. 优化查询逻辑
    • 将多个独立$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.*)以尝试利用前缀索引。
  2. 调整索引策略
    • 优化现有索引字段顺序,尝试创建{domain:1, deleted:1, specialItem:1, textSearch:1}的多键索引,减少FETCH阶段的文档扫描量。
    • 使用覆盖索引:若查询仅需返回特定字段,创建包含目标字段的索引(如{domain:1, deleted:1, specialItem:1, textSearch:1, itemNumber:1}),避免读取完整文档。
  3. 调整MongoDB配置
    • 调高WiredTiger缓存:修改mongod.conf中storage.wiredTiger.engineConfig.cacheSizeGB参数(比如设为4GB,不超过系统可用内存的80%),减少磁盘IO。
    • 设置查询批处理大小:在Java代码中为查询添加q.batchSize(50),避免一次性读取过多数据。
  4. 数据预处理
    • 将textSearch数组的元素合并为单个字符串字段(如textSearchKeywords),用空格分隔,减少MongoDB对数组的遍历次数。
    • 若业务允许,提前将高频查询关键词转为布尔字段(如hasHandle、hasMount),直接匹配布尔字段替代正则查询。

内容的提问来源于stack exchange,提问作者user3529850

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 16:35:00