一、慢查询原理

MongoDB 的慢查询依赖数据库操作分析器(Profiler),它可以记录所有或部分操作,包括慢查询。慢查询日志通常记录在 system.profile 集合中,也可以在日志文件中查看。


二、慢查询分析配置

1. 设置慢查询阈值

MongoDB 默认慢查询阈值为 100 毫秒(即超过 100ms 的操作被视为慢查询)。可以通过参数调整:

  • 临时设置(shell)

    db.setProfilingLevel(1, { slowms: 50 }) // 设置阈值为 50ms
    
    • 0:关闭分析器
    • 1:仅记录慢查询
    • 2:记录所有操作
  • 永久设置(配置文件)
    在 mongod.conf 文件中添加:

    operationProfiling:
      mode: slowOp
      slowOpThresholdMs: 50
    

三、查看慢查询日志

1. 查看最近的慢查询

use mydb
db.system.profile.find({ millis: { $gt: 50 } }).sort({ ts: -1 }).limit(10)
  • millis 字段表示操作耗时(毫秒)
  • ts 字段表示时间戳

2. 查看所有慢查询

db.system.profile.find({ millis: { $gt: 100 } })

3. 查看慢查询详情(如执行计划)

慢查询文档通常包含:

  • 查询语句
  • 索引信息
  • 扫描文档数
  • 返回文档数
  • 执行时间

四、日志文件分析(适用于生产环境)

MongoDB 的日志文件也会记录慢查询(含慢写/慢聚合),示例:

2024-05-01T10:15:23.123+0800 I COMMAND  [conn123] command mydb.mycollection command: find ... planSummary: IXSCAN ... keysExamined:1000 docsExamined:1000 cursorExhausted:1 numYields:8 nreturned:10 queryHash:... locks:{ ... } protocol:op_msg 117ms

分析步骤:

  1. grep 慢查询关键字(如 msCOMMANDplanSummary
  2. 关注 IXSCAN(索引扫描)、COLLSCAN(全表扫描)、docsExamined 等字段
  3. 重点排查耗时长、扫描文档多的语句

五、慢查询优化建议

  1. 建立合理索引

    • 使用 explain() 分析查询计划,避免全表扫描(COLLSCAN
    • 优化索引字段顺序,覆盖查询
  2. 优化查询语句

    • 避免 $regex、函数、复杂表达式作为查询条件
    • 尽量使用等值或范围查询
  3. 分页与排序优化

    • 大分页建议用“游标”方式
    • 排序字段需有索引
  4. 硬件与参数优化

    • 增加内存、SSD磁盘
    • 调整 wiredTigerCacheSizeGB 等参数
  5. 定期分析与清理

    • 定期查看慢查询日志,监控性能
    • 清理无用索引,避免索引膨胀

六、典型分析流程

  1. 开启慢查询分析器(如 db.setProfilingLevel(1, {slowms: 50})
  2. 定期查询 system.profile 集合,筛选耗时长的操作
  3. 用 explain() 分析慢查询语句,查找瓶颈
  4. 优化索引和语句,再次测试
  5. 关注日志文件慢查询记录,结合监控工具(如 MongoDB Atlas、Prometheus)

七、工具推荐

  • 官方工具:MongoDB Compass(可查看慢查询与分析计划)
  • 第三方:mtools、Percona Monitoring and Management、ELK日志分析

八、常用命令速查

// 开启慢查询分析
db.setProfilingLevel(1, { slowms: 50 })

// 查看慢查询
db.system.profile.find({ millis: { $gt: 50 } }).sort({ ts: -1 }).limit(10)

// 查看某条语句执行计划
db.collection.find(...).explain("executionStats")

九、查询优化核心原理

  1. 合理索引:索引让查询更快,减少扫描的数据量。
  2. 查询语句优化:结构合理、条件明确的查询效率更高。
  3. 数据模型优化:模式设计影响查询效率和存储空间。
  4. 硬件/参数优化:资源充足、参数合理也能提升查询性能。

十、常用查询优化方法

1. 索引优化

  • 创建合适的索引

    db.collection.createIndex({ username: 1 })
    
  • 复合索引
    针对多条件查询,建立复合索引。

    db.collection.createIndex({ username: 1, created_at: -1 })
    
  • 覆盖索引
    查询只涉及索引字段时,避免回表,提高速度。

    db.collection.find({ username: "Tom" }, { username: 1, _id: 0 })
    
  • 避免索引失效

    • 查询条件类型需与索引一致(如数字与字符串类型需匹配)
    • 查询字段需包含在索引前缀中

2. 查询语句优化

  • 避免全表扫描

    • 查询条件必须命中索引
    • 避免 $regex、函数、表达式等作为索引字段条件
  • 分页优化

    • 大分页建议用“游标”或“锚点”方式
    • 传统 skip 随页码增大性能下降
    // 游标分页
    db.collection.find({ _id: { $gt: last_id } }).sort({ _id: 1 }).limit(20)
    
  • 排序优化

    • 排序字段需有索引,避免内存排序
    • 多字段排序时,建立对应复合索引
  • 字段投影

    • 只返回需要的字段,减少网络和内存消耗
    db.collection.find({}, { username: 1, email: 1, _id: 0 })
    

3. 数据模型优化

  • 合理嵌套与引用

    • 频繁访问的子文档建议嵌入主文档
    • 大型或不常用子文档建议分表引用
  • 文档大小控制

    • 避免单文档过大(MongoDB 单文档最大 16MB)

4. 查询计划分析

  • 使用 explain() 查看查询执行计划
    db.collection.find({ username: "Tom" }).explain("executionStats")
    
    • COLLSCAN:全表扫描,需优化
    • IXSCAN:索引扫描,性能优

十一、慢查询分析与优化

  • 开启慢查询分析器,定位慢语句
    db.setProfilingLevel(1, { slowms: 50 })
    db.system.profile.find({ millis: { $gt: 50 } }).sort({ ts: -1 })
    
  • 对慢查询使用 explain() 分析
  • 优化索引与语句结构

十二、常见查询优化案例

1. 查询未命中索引导致慢查询

db.collection.find({ age: "25" }) // age 字段为数字类型,查询为字符串,索引失效

优化:确保类型一致。

2. 排序慢

db.collection.find({ status: 1 }).sort({ created_at: -1 }) // created_at 无索引

优化:为排序字段建立索引。

3. 大分页慢

db.collection.find().skip(10000).limit(10)

优化:用游标分页。


十三、监控与工具

  • MongoDB Compass:可视化分析查询计划与慢查询
  • mtools:日志分析
  • Prometheus + Grafana:监控慢查询和资源消耗

十四、查询优化最佳实践

  1. 所有高频查询都需索引覆盖
  2. 定期分析慢查询,及时优化
  3. 索引数量适量,过多影响写入性能
  4. 分页、排序需有索引支持
  5. 只查询需要的字段
  6. 合理设计数据模型,避免大文档

十五、常用命令速查

// 创建索引
db.collection.createIndex({ field: 1 })

// 查看索引
db.collection.getIndexes()

// 查询执行计划
db.collection.find({ ... }).explain("executionStats")

// 慢查询分析
db.setProfilingLevel(1, { slowms: 50 })
db.system.profile.find({ millis: { $gt: 50 } })

// 删除索引
db.collection.dropIndex("field_1")

十六、MongoDB 锁机制简介

MongoDB 采用多级锁机制,主要包括:

  • 全局锁(Global Lock):影响整个实例,早期版本(2.x)为主,3.x及以后已优化。
  • 数据库锁(Database Lock):影响单个数据库。
  • 集合锁(Collection Lock):影响单个集合。
  • 文档级锁(Document Lock):最新 WiredTiger 引擎支持更细粒度的锁(文档级别)。

当前主流版本(WiredTiger)锁粒度更细,性能更高。


十七、锁状态查看命令

1. 查看全局锁状态

db.serverStatus().globalLock

输出示例:

{
  "totalTime": 123456789,
  "currentQueue": {
    "total": 2,
    "readers": 1,
    "writers": 1
  },
  "activeClients": {
    "total": 5,
    "readers": 3,
    "writers": 2
  }
}
  • currentQueue:当前等待锁的读/写请求数
  • activeClients:当前持有锁的读/写客户端数

2. 查看锁详细信息(WiredTiger引擎)

db.serverStatus().wiredTiger

关注 concurrentTransactions 部分:

"wiredTiger": {
  "concurrentTransactions": {
    "read": {
      "out": 0,
      "available": 128,
      "totalTickets": 128
    },
    "write": {
      "out": 0,
      "available": 128,
      "totalTickets": 128
    }
  }
}
  • out:当前正在进行的事务数
  • available:剩余可用事务数

3. 查看当前操作与锁等待

db.currentOp()

输出包含 waitingForLock 字段的操作即为正在等待锁。


十九、锁相关参数与指标

  • 锁等待队列长度
    db.serverStatus().globalLock.currentQueue
  • 锁等待时间
    db.serverStatus().globalLock.totalTime
  • 活动客户端数
    db.serverStatus().globalLock.activeClients
  • WiredTiger 事务票据
    db.serverStatus().wiredTiger.concurrentTransactions

二十、锁分析典型流程

  1. 监控锁等待队列和活动客户端数

    • 队列持续增长,说明有锁竞争,需优化并发或语句。
  2. 分析慢查询与锁等待

    • 用 db.currentOp() 查找是否有大量操作 waitingForLock:true
    • 结合慢查询日志,定位锁等待的具体语句。
  3. 关注写操作锁竞争

    • 写操作多、集合更新频繁时容易产生锁竞争。
  4. 分析资源瓶颈

    • 锁等待可能因硬件资源不足(如磁盘IO、CPU)、索引缺失等导致。

二十一、锁优化建议

  1. 升级 WiredTiger 引擎

    • 现代版本支持文档级锁,显著提升并发性能。
  2. 合理设计索引

    • 减少全表扫描,降低锁持有时间。
  3. 优化写入模式

    • 批量写入、减少大事务、分散高并发写入。
  4. 分片集群扩展

    • 水平扩展,分散写压力,降低单集合锁竞争。
  5. 硬件升级

    • 增加内存、SSD磁盘,提升整体性能。
  6. 定期监控与分析

    • 用监控工具(如 MongoDB Ops Manager、Prometheus)持续关注锁相关指标。

二十二、常用锁分析命令速查

// 查看全局锁状态
db.serverStatus().globalLock

// 查看 WiredTiger 事务并发
db.serverStatus().wiredTiger.concurrentTransactions

// 查看当前操作及锁等待
db.currentOp({ "waitingForLock": true })

// 查看慢查询与锁相关日志
db.system.profile.find({ millis: { $gt: 100 }, waitingForLock: true })

二十三、工具推荐

  • MongoDB Ops Manager / Cloud Manager:可视化锁分析
  • Prometheus + Grafana:可自定义锁等待指标监控
  • mtools:分析 MongoDB 日志,提取锁等待信息

二十四、锁等待案例分析

案例一:批量写入导致写锁竞争

场景描述
某业务高峰期,后台批量插入数据,每次插入几千条,导致应用响应变慢,甚至出现短暂“卡死”现象。

分析步骤

  1. 监控锁等待队列

    db.serverStatus().globalLock.currentQueue
    

    发现 writers 队列数持续升高。

  2. 查看当前操作

    db.currentOp({ "waitingForLock": true })
    

    发现有多个批量插入操作在等待写锁。

  3. 检查慢查询

    db.system.profile.find({ waitingForLock: true }).sort({ ts: -1 }).limit(10)
    

    发现慢写入操作均有 waitingForLock: true,耗时高达几秒。

  4. 查看写入模式
    批量插入使用的是单条写入循环,未使用批量API。

原因分析

  • 大量写操作并发,且单条写入,导致集合锁频繁竞争。
  • 未使用批量写入API,锁持有时间长。

案例二:全表扫描慢查询导致读锁竞争

场景描述
某查询接口偶尔响应极慢,数据库CPU飙高。业务为复杂条件查询,缺乏索引。

分析步骤

  1. 监控锁等待队列

    db.serverStatus().globalLock.currentQueue
    

    发现 readers 队列数升高。

  2. 查看慢查询

    db.system.profile.find({ millis: { $gt: 500 } }).sort({ ts: -1 })
    

    发现某些查询为 COLLSCAN(全表扫描),耗时数秒。

  3. 分析查询计划

    db.collection.find({ ... }).explain("executionStats")
    

    发现 docsExamined 非常大,未命中索引。

  4. 查看锁等待操作

    db.currentOp({ "waitingForLock": true })
    

    发现有大量读操作在等待锁。

原因分析

  • 查询未命中索引,导致全表扫描,读锁持有时间长,其他读操作需等待。

二十五、锁优化实战方法

1. 优化写入方式

  • 使用批量写入API

    db.collection.insertMany([ ... ])
    

    批量写入一次性获取锁,减少锁竞争。

  • 拆分写入压力
    分散高并发写入到不同集合或分片。

2. 索引优化

  • 为高频查询建立合适索引
    减少全表扫描,降低锁持有时间。

    db.collection.createIndex({ field: 1 })
    
  • 定期清理无用或冗余索引
    索引过多会影响写入性能。

3. 查询语句优化

  • 避免复杂表达式、正则、函数直接作为条件。
  • 只查询需要的字段,减少锁时间。

4. 分片集群扩展

  • 水平扩展分片,分散读写压力,降低单集合锁竞争。

5. 升级存储引擎/版本

  • 使用 WiredTiger 引擎,支持文档级锁,显著提升并发。
  • 保持 MongoDB 版本为最新稳定版。

6. 硬件资源优化

  • 增加内存、SSD磁盘,提升整体吞吐。
  • 监控磁盘IO和CPU,资源瓶颈也会加剧锁等待。

7. 定期监控与报警

  • 用监控工具(如 MongoDB Ops Manager、Prometheus)设置锁等待阈值报警。
  • 定期分析 db.serverStatus().globalLock 和慢查询日志。

二十六、锁优化实战流程总结

  1. 监控锁等待指标db.serverStatus().globalLock
  2. 定位锁等待操作db.currentOp({ "waitingForLock": true })
  3. 分析慢查询与锁关系db.system.profile.find({ waitingForLock: true })
  4. 优化索引/语句/写入方式:减少锁持有时间
  5. 扩展分片/升级引擎/优化硬件:提升整体并发
  6. 持续监控与自动报警:及时发现锁竞争问题

二十七、常用命令速查

// 查看锁队列和活跃客户端
db.serverStatus().globalLock

// 查看当前等待锁的操作
db.currentOp({ "waitingForLock": true })

// 查看慢查询中的锁等待
db.system.profile.find({ waitingForLock: true })

// 创建索引优化锁
db.collection.createIndex({ field: 1 })

知识传递、互通有无,创作不易,点点关注!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐