MongoDB 慢查询深度优化指南(检测→分析→调优)
一、慢查询检测体系
1. 三层检测机制
graph TDA[实时检测] --> 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: snappyindexConfig:prefixCompression: truesystemLog:logAppend: truepath: /var/log/mongodb/mongod.logoperationProfiling:mode: slowOpslowOpThresholdMs: 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
-
优化步骤:
- 创建复合索引:
collection.createIndex(Indexes.compoundIndex(Indexes.ascending("user_id"), Indexes.descending("create_time") ));
- 改写查询:
Bson query = and(eq("user_id", userId),gte("create_time", startDate),lte("create_time", endDate) );
- 执行计划验证:
"winningPlan": {"stage": "IXSCAN","indexName": "user_id_1_create_time_-1" }
案例2:物联网设备数据聚合慢
-
问题现象:
- 每日设备状态统计耗时5分钟+
- 内存溢出导致频繁磁盘交换
-
优化方案:
- 启用时间分桶模式:
@Document public class DeviceStatus {private String deviceId;private int hourBucket; // 小时级分桶private List<MinuteData> minutes; }
- 优化聚合管道:
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")) );
- 配置
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 {@Testvoid 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显示无持续高负载 |
安全 | 开启角色访问控制 | 无匿名访问权限 |