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 显示 COLLSCANdocsExamined 远大于 nreturned

解决方案

  • 创建适当的索引
  • 优化查询条件,确保使用索引
  • 考虑分片集群,分散查询负载

2. 索引不匹配

症状keysExamined 远大于 nreturnedplanSummary 显示 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. 监控慢查询: 观察慢查询日志,确认该查询不再被记录为慢查询。