Skip to content

MongoDB 慢查询分析 ​

开启慢查询日志 ​

配置文件方式 ​

  1. 编辑 MongoDB 配置文件:

    yaml
    systemLog:
      destination: file
      path: /var/log/mongodb/mongod.log
      logAppend: true
    
    operationProfiling:
      slowOpThresholdMs: 100  # 慢查询阈值,单位毫秒
      mode: slowOp  # 只记录慢查询
  2. 重启 MongoDB:

    bash
    systemctl 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 分析 ​

  1. 打开 MongoDB Compass
  2. 连接到 MongoDB 实例
  3. 导航到 "Performance" 标签页
  4. 查看慢查询列表
  5. 点击具体查询,查看查询计划和详细信息
  6. 使用 "Explain Plan" 分析查询性能

使用第三方工具分析 ​

ELK Stack ​

  1. 配置 Filebeat:

    yaml
    filebeat.inputs:
    - type: log
      paths:
        - /var/log/mongodb/mongod.log
      json.keys_under_root: true
      json.overwrite_keys: true
    
    output.elasticsearch:
      hosts: ["localhost:9200"]
  2. 配置 Kibana 可视化:

    • 创建索引模式 mongodb-*
    • 配置仪表盘,包括:
      • 慢查询数量趋势图
      • 慢查询类型分布
      • 平均查询时间
      • 全集合扫描统计

Prometheus + Grafana ​

  1. 配置 MongoDB Exporter
  2. 配置 Prometheus 抓取规则
  3. 配置 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,日志显示全集合扫描。

优化步骤 ​

  1. 分析查询计划:

    javascript
    db.users.find({ age: { $gt: 30 } }).explain("executionStats")
  2. 创建索引:

    javascript
    db.users.createIndex({ age: 1 })
  3. 验证优化效果:

    javascript
    db.users.find({ age: { $gt: 30 } }).explain("executionStats")
  4. 监控慢查询: 观察慢查询日志,确认该查询不再被记录为慢查询。