外观
MongoDB 慢查询分析
开启慢查询日志
配置文件方式
编辑 MongoDB 配置文件:
yamlsystemLog: destination: file path: /var/log/mongodb/mongod.log logAppend: true operationProfiling: slowOpThresholdMs: 100 # 慢查询阈值,单位毫秒 mode: slowOp # 只记录慢查询重启 MongoDB:
bashsystemctl restart mongod
命令行方式
bash
mongod --operationProfiling.slowOpThresholdMs 100 --operationProfiling.mode slowOp运行时配置
javascript
// 连接到 MongoDB
mongosh
// 开启慢查询日志
use admin
db.setProfilingLevel(1, { slowms: 100 })
// 查看当前配置
db.getProfilingStatus()慢查询日志格式
日志示例
json
{
"t": { "$date": "2023-01-01T00:00:00.000Z" },
"s": "I",
"c": "COMMAND",
"id": 51803,
"ctx": "conn123",
"msg": "Slow query",
"attr": {
"type": "command",
"ns": "mydb.mycollection",
"command": {
"find": "mycollection",
"filter": { "name": "test" },
"lsid": { "id": { "$uuid": "..." } },
"$db": "mydb"
},
"planSummary": "COLLSCAN",
"keysExamined": 0,
"docsExamined": 10000,
"cursorExhausted": true,
"numYields": 0,
"nreturned": 1,
"reslen": 123,
"locks": {
"ReplicationStateTransition": { "acquireCount": { "w": 1 } },
"Global": { "acquireCount": { "r": 1 } },
"Database": { "acquireCount": { "r": 1 } },
"Collection": { "acquireCount": { "r": 1 } }
},
"storage": {
"data": {
"bytesRead": 1024000,
"timeReadingMicros": 5000
}
},
"protocol": "op_msg",
"durationMillis": 150
}
}关键字段说明
- durationMillis:查询执行时间,单位毫秒
- planSummary:查询计划摘要,如 COLLSCAN(全集合扫描)或 IXSCAN(索引扫描)
- keysExamined:检查的索引键数量
- docsExamined:检查的文档数量
- nreturned:返回的文档数量
- reslen:结果大小,单位字节
- locks:锁信息,包括锁类型和获取次数
- storage:存储层信息,包括读取的字节数和读取时间
分析慢查询
使用 mongosh 分析
javascript
// 连接到 MongoDB
mongosh
// 查看慢查询日志
use mydb
db.system.profile.find({ durationMillis: { $gt: 100 } }).sort({ ts: -1 })
// 按查询类型分组统计
use mydb
db.system.profile.aggregate([
{ $match: { durationMillis: { $gt: 100 } } },
{ $group: { _id: "$command.find", count: { $sum: 1 }, avgDuration: { $avg: "$durationMillis" } } },
{ $sort: { count: -1 } }
])
// 查找全集合扫描的慢查询
use mydb
db.system.profile.find({ planSummary: "COLLSCAN", durationMillis: { $gt: 100 } }).sort({ durationMillis: -1 })使用 MongoDB Compass 分析
- 打开 MongoDB Compass
- 连接到 MongoDB 实例
- 导航到 "Performance" 标签页
- 查看慢查询列表
- 点击具体查询,查看查询计划和详细信息
- 使用 "Explain Plan" 分析查询性能
使用第三方工具分析
ELK Stack
配置 Filebeat:
yamlfilebeat.inputs: - type: log paths: - /var/log/mongodb/mongod.log json.keys_under_root: true json.overwrite_keys: true output.elasticsearch: hosts: ["localhost:9200"]配置 Kibana 可视化:
- 创建索引模式
mongodb-* - 配置仪表盘,包括:
- 慢查询数量趋势图
- 慢查询类型分布
- 平均查询时间
- 全集合扫描统计
- 创建索引模式
Prometheus + Grafana
- 配置 MongoDB Exporter
- 配置 Prometheus 抓取规则
- 配置 Grafana 仪表盘,包括:
- 慢查询数量
- 平均查询执行时间
- 慢查询类型分布
- 索引使用情况
慢查询优化步骤
1. 分析查询计划
使用 explain() 方法分析查询计划:
javascript
// 连接到 MongoDB
mongosh
// 分析查询计划
use mydb
db.mycollection.find({ name: "test" }).explain("executionStats")2. 优化索引设计
根据查询模式创建适当的索引:
javascript
// 创建单字段索引
db.mycollection.createIndex({ name: 1 })
// 创建复合索引
db.mycollection.createIndex({ name: 1, age: -1 })
// 创建文本索引
db.mycollection.createIndex({ content: "text" })3. 优化查询语句
使用投影限制返回字段:
javascript
db.mycollection.find({ name: "test" }, { name: 1, age: 1, _id: 0 })
- **使用 $limit 限制结果集大小**:
```javascript
db.mycollection.find({ name: "test" }).limit(10)使用 $sort 优化排序:
javascript
db.mycollection.find({ name: "test" }).sort({ createdAt: -1 })
- **使用 $match 优化聚合查询**:
```javascript
db.mycollection.aggregate([
{ $match: { name: "test" } },
{ $group: { _id: "$age", count: { $sum: 1 } } }
])4. 优化数据模型
- 嵌入相关数据:减少关联查询
- 使用合适的数据类型:避免不必要的数据类型转换
- 合理设计文档结构:避免过大的文档
5. 监控和调整
- 定期监控慢查询:及时发现和解决性能问题
- 调整慢查询阈值:根据业务需求调整慢查询阈值
- 优化硬件资源:根据需要增加 CPU、内存和存储资源
慢查询分析最佳实践
1. 设置合理的慢查询阈值
- 根据业务需求设置合适的慢查询阈值
- 对于交互式应用,建议设置为 100-200ms
- 对于批处理应用,建议设置为 1000ms 或更高
2. 定期分析慢查询
- 定期分析慢查询日志,识别性能瓶颈
- 重点关注全集合扫描和高文档检查数的查询
- 跟踪慢查询的变化趋势
3. 使用覆盖索引
- 创建包含查询所需所有字段的索引,避免回表查询
- 覆盖索引可以显著提高查询性能
4. 避免全集合扫描
- 确保查询使用了合适的索引
- 对于无法使用索引的查询,考虑优化数据模型或查询方式
5. 监控索引使用情况
- 使用
db.collection.stats()查看索引使用情况 - 定期清理 unused 索引,减少写操作开销
6. 优化聚合查询
- 使用
$match尽早过滤数据 - 使用
$sort和$limit优化排序和分页 - 考虑使用
$lookup替代多次查询
常见慢查询场景和解决方案
1. 全集合扫描
症状:planSummary 显示 COLLSCAN,docsExamined 远大于 nreturned
解决方案:
- 创建适当的索引
- 优化查询条件,确保使用索引
- 考虑分片集群,分散查询负载
2. 索引不匹配
症状:keysExamined 远大于 nreturned,planSummary 显示 IXSCAN
解决方案:
- 优化索引设计,创建更精确的索引
- 调整查询条件,确保使用最匹配的索引
- 使用
hint()指定索引
3. 排序性能差
症状:sort 阶段耗时较长,docsExamined 较大
解决方案:
- 创建包含排序字段的复合索引
- 限制排序结果集大小
- 考虑在应用层进行排序
4. 聚合查询慢
症状:聚合查询执行时间长,资源消耗大
解决方案:
- 优化聚合管道,使用
$match尽早过滤数据 - 使用
$sort和$limit优化排序和分页 - 考虑使用
$lookup替代多次查询
常见问题(FAQ)
Q1: 如何确定慢查询阈值?
A1: 确定慢查询阈值的方法:
- 根据业务需求和用户体验要求设置
- 监控正常查询的执行时间,设置略高于正常查询时间的阈值
- 定期调整阈值,根据实际情况优化
Q2: 慢查询日志会影响性能吗?
A2: 慢查询日志会对 MongoDB 性能产生一定影响,影响程度取决于慢查询的数量和日志级别。建议:
- 只记录真正的慢查询,设置合理的阈值
- 避免使用
all模式,只使用slowOp模式 - 定期清理慢查询日志,避免日志文件过大
Q3: 如何处理大量慢查询?
A3: 处理大量慢查询的方法:
- 优先处理执行时间最长和频率最高的慢查询
- 批量优化相似的慢查询
- 考虑分片集群,分散查询负载
- 优化硬件资源,增加 CPU、内存和存储
Q4: 如何验证慢查询优化效果?
A4: 验证慢查询优化效果的方法:
- 比较优化前后的查询执行时间
- 监控慢查询数量的变化
- 检查查询计划的变化,确保使用了合适的索引
- 监控系统资源使用情况,如 CPU、内存和磁盘 I/O
Q5: 如何监控慢查询?
A5: 监控慢查询的方法:
- 使用 MongoDB 内置的慢查询日志
- 使用 MongoDB Compass 查看慢查询
- 使用第三方监控工具,如 Prometheus + Grafana、ELK Stack 等
- 设置告警规则,当慢查询数量超过阈值时通知管理员
Q6: 如何优化复杂查询?
A6: 优化复杂查询的方法:
- 分解复杂查询为多个简单查询
- 使用聚合管道优化复杂查询
- 创建合适的索引
- 优化数据模型,减少查询复杂度
Q7: 如何处理分片集群中的慢查询?
A7: 处理分片集群中慢查询的方法:
- 确保查询包含分片键,避免广播查询
- 优化索引设计,确保每个分片都使用了合适的索引
- 监控每个分片的慢查询情况
- 考虑调整分片键,提高查询性能
Q8: 如何避免慢查询?
A8: 避免慢查询的方法:
- 设计合适的索引
- 优化查询语句
- 合理设计数据模型
- 定期监控和优化慢查询
- 确保硬件资源充足
示例:慢查询优化案例
问题描述
查询 db.users.find({ age: { $gt: 30 } }) 执行时间超过 500ms,日志显示全集合扫描。
优化步骤
分析查询计划:
javascriptdb.users.find({ age: { $gt: 30 } }).explain("executionStats")创建索引:
javascriptdb.users.createIndex({ age: 1 })验证优化效果:
javascriptdb.users.find({ age: { $gt: 30 } }).explain("executionStats")监控慢查询: 观察慢查询日志,确认该查询不再被记录为慢查询。
