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
-
优化步骤:
- 创建复合索引:
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 {
@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显示无持续高负载 |
| 安全 | 开启角色访问控制 | 无匿名访问权限 |

浙公网安备 33010602011771号