MongoDB 慢查询优化

MongoDB 慢查询深度优化指南(检测→分析→调优)


一、慢查询检测体系

1. 三层检测机制

graph TD A[实时检测] --> B[Profiling日志] C[持续监控] --> D[Database Profiler] E[深度分析] --> F[Explain+执行计划]

2. 生产级Profile配置

// Java驱动开启Profiling
MongoDatabase adminDb = mongoClient.getDatabase("admin");
Document profileCommand = new Document()
    .append("setProfilingLevel", 1)
    .append("slowms", 100)  // 100ms阈值
    .append("sampleRate", 0.5); // 采样率
adminDb.runCommand(profileCommand);

3. 监控指标关联

# 慢查询关键指标(Prometheus)
mongodb_ss_opLatencies_reads_latency{instance="mongo1:9100"} > 100
mongodb_ss_opLatencies_writes_latency{instance="mongo1:9100"} > 200
mongodb_ss_cursor_open_total{state="timedOut"} > 0

二、执行计划深度解析

1. Explain结果关键字段

{
  "queryPlanner": {
    "winningPlan": {
      "stage": "COLLSCAN", // 全表扫描
      "filter": {"$match": {...}},
      "direction": "forward"
    },
    "rejectedPlans": [...] 
  },
  "executionStats": {
    "executionTimeMillis": 128,
    "totalKeysExamined": 0,  // 未使用索引
    "totalDocsExamined": 1000000,
    "executionStages": {
      "stage": "COLLSCAN",
      "nReturned": 10,
      "works": 1000002
    }
  }
}

2. 性能问题模式识别

// 识别低效查询模式
public class QueryAnalyzer {
    public void analyzeExplainResult(Document explainResult) {
        if ("COLLSCAN".equals(explainResult.get("stage"))) {
            if (explainResult.getInteger("totalDocsExamined") > 10000) {
                logger.warn("全表扫描告警: {}", explainResult);
            }
        }
        if (explainResult.getDouble("executionTimeMillis") > 100) {
            logger.error("慢查询告警: {}ms", explainResult.getDouble("executionTimeMillis"));
        }
    }
}

3. 索引覆盖检查

// 验证覆盖查询
public boolean isCoveredQuery(Document explainResult) {
    return explainResult.getInteger("totalDocsExamined") == 0 
           && explainResult.getInteger("totalKeysExamined") > 0;
}

// 创建覆盖索引示例
collection.createIndex(Indexes.compoundIndex(
    Indexes.ascending("status", "create_time"),
    Indexes.include("order_no")
));

三、索引优化实战

1. 索引选择矩阵

查询模式 推荐索引类型 示例
等值查询+范围排序 复合索引(等值在前) {status:1, create_time:-1}
多字段排序 复合索引匹配排序顺序 {category:1, price:-1}
正则表达式搜索 前缀索引+Collation {title:1}, collation{locale:'en', strength:2}
全文检索 文本索引 {description:"text"}

2. 索引性能验证

// 索引效率对比测试框架
public class IndexBenchmark {
    public void runBenchmark(String indexField, Query query) {
        collection.dropIndex(indexField);
        
        // 无索引测试
        long timeWithout = measureQueryTime(query);
        
        collection.createIndex(Indexes.ascending(indexField));
        
        // 有索引测试
        long timeWith = measureQueryTime(query);
        
        logger.info("索引[{}]性能提升: {}ms → {}ms ({}%)", 
            indexField, timeWithout, timeWith, 
            (timeWithout - timeWith)*100/timeWithout);
    }
}

3. 索引维护策略

# 索引重建维护脚本
mongo --eval "db.getCollection('orders').reIndex()" 

# 后台索引构建
db.orders.createIndex({product_id:1}, {background: true})

四、查询优化技巧

1. 分页优化方案对比

// 传统分页(性能差)
FindIterable<Document> results = collection.find(query)
    .skip((pageNum - 1) * pageSize)
    .limit(pageSize);

// 游标分页(推荐)
Bson lastId = ...; // 上一页最后记录的_id
FindIterable<Document> results = collection.find(and(query, gt("_id", lastId)))
    .limit(pageSize)
    .sort(Sorts.ascending("_id"));

2. 聚合管道优化

// 低效管道
Aggregates.match(where("status").is("active")),
Aggregates.unwind("$items"),
Aggregates.group("$items.category")

// 优化后(提前过滤)
Aggregates.match(and(
    where("status").is("active"),
    where("items").exists(true)
)),
Aggregates.project(fields(
    include("items"),
    computed("filteredItems", 
        filter("$items", "item", where("item.stock").gt(0))
)),
Aggregates.unwind("$filteredItems")

3. 内存控制技巧

// 允许磁盘缓存(避免内存溢出)
AggregationOptions options = AggregationOptions.builder()
    .allowDiskUse(true)
    .build();

collection.aggregate(pipeline, options);

五、系统级调优

1. 内存配置黄金法则

# mongod.conf 生产配置
storage:
  wiredTiger:
    engineConfig:
      cacheSizeGB: 24  # 总内存的50-60%
    collectionConfig:
      blockCompressor: snappy
    indexConfig:
      prefixCompression: true

systemLog:
  logAppend: true
  path: /var/log/mongodb/mongod.log

operationProfiling:
  mode: slowOp
  slowOpThresholdMs: 100

2. 锁竞争优化

# 查看当前锁状态
db.currentOp(true).inprog.forEach(
  function(op) { 
    if (op.locks) printjson(op.locks)
  }
)

# 优化方案:
# 1. 使用$atomic替代大范围更新
# 2. 分片集群分散压力
# 3. 升级到MongoDB 4.0+使用乐观并发控制

六、典型案例分析

案例1:电商订单查询慢

  • 问题现象

    • 按用户ID+时间范围查询订单耗时>1s
    • 存在大量COLLSCAN
  • 优化步骤

    1. 创建复合索引:
    collection.createIndex(Indexes.compoundIndex(
        Indexes.ascending("user_id"), 
        Indexes.descending("create_time")
    ));
    
    1. 改写查询:
    Bson query = and(
        eq("user_id", userId),
        gte("create_time", startDate),
        lte("create_time", endDate)
    );
    
    1. 执行计划验证:
    "winningPlan": {
      "stage": "IXSCAN",
      "indexName": "user_id_1_create_time_-1"
    }
    

案例2:物联网设备数据聚合慢

  • 问题现象

    • 每日设备状态统计耗时5分钟+
    • 内存溢出导致频繁磁盘交换
  • 优化方案

    1. 启用时间分桶模式:
    @Document
    public class DeviceStatus {
        private String deviceId;
        private int hourBucket; // 小时级分桶
        private List<MinuteData> minutes; 
    }
    
    1. 优化聚合管道:
    List<Bson> pipeline = Arrays.asList(
        match(and(
            eq("deviceId", deviceId), 
            gte("hourBucket", startHour),
            lte("hourBucket", endHour)
        )),
        unwind("$minutes"),
        group(null, 
            avg("avgTemp").avg("$minutes.temperature"),
            max("maxVoltage").max("$minutes.voltage")
        )
    );
    
    1. 配置allowDiskUse并添加索引:
    collection.createIndex(Indexes.compoundIndex(
        Indexes.ascending("deviceId"),
        Indexes.ascending("hourBucket")
    ));
    

七、自动化优化工具链

1. 慢查询分析工具

# 使用mtools进行日志分析
mloginfo --queries mongod.log
mlogfilter mongod.log --slow --json | mplotqueries

# 输出可视化图表:
# - 查询类型分布
# - 执行时间热力图
# - 扫描/返回文档比例

2. 索引建议工具

// 使用index advisor
db.collection.aggregate([
    {$indexStats: {}},
    {$match: {accesses: {$gt: 1000}}},
    {$sort: {"accesses.ops": -1}}
])

3. 自动化索引管理

// 基于查询模式的自动索引推荐
public class AutoIndexer {
    public void analyzeQueries() {
        List<Document> slowQueries = profileCollection.find(
            gt("millis", 100)
        ).into(new ArrayList<>());
        
        slowQueries.forEach(query -> {
            QueryAnalyzer analyzer = new QueryAnalyzer(query);
            analyzer.recommendIndex();
        });
    }
}

八、预防性优化策略

1. 开发规范

1. **查询规范**:
   - 禁止不带条件的`find({})`
   - `$in`数组大小不超过1000
   - 更新操作必须带条件

2. **索引规范**:
   - 组合索引不超过4个字段
   - 单集合索引数不超过10个
   - 索引内存占用不超过工作集大小

3. **设计规范**:
   - 文档大小不超过16MB
   - 嵌套层级不超过5层
   - 时间序列数据必须分桶

2. 压力测试方案

// 使用JMeter进行负载测试
public class MongoStressTest {
    @Test
    void testQueryPerformance() {
        MongoTemplate template = ...;
        IntStream.range(0, 10000).parallel().forEach(i -> {
            Query query = Query.query(
                where("status").is("active")
                    .and("createTime").gte(LocalDateTime.now().minusDays(7))
            ).with(Sort.by(Sort.Direction.DESC, "priority"));
            
            template.find(query, Order.class); // 记录响应时间
        });
    }
}

3. 容量规划模型

// 数据增长预测算法
public class CapacityPlanner {
    public StorageInfo forecastGrowth(String collectionName) {
        Document stats = db.runCommand(new Document("collStats", collectionName));
        double dailyGrowth = stats.getDouble("avgObjSize") * 
            getInsertOpsPerDay(collectionName);
        
        return new StorageInfo(
            LocalDate.now().plusDays(30),
            stats.getDouble("size") + dailyGrowth * 30
        );
    }
}

九、深度调优清单

优化方向 检查项 达标标准
索引 所有查询都使用索引 explain显示IXSCAN
内存 工作集完全放入内存 wiredTiger缓存命中率>95%
查询 无超过100ms的慢查询 Profiling日志无告警
连接 连接池利用率<80% maxPoolSize配置合理
存储 磁盘IO延迟<10ms iostat显示无持续高负载
安全 开启角色访问控制 无匿名访问权限
posted @ 2025-02-12 18:33  J九木  阅读(145)  评论(0)    收藏  举报