跳到主要内容

性能监控与调优

性能优化是 MongoDB 运维的核心技能。本章介绍如何监控 MongoDB 性能并进行调优。

性能监控概述

为什么需要监控?

监控指标分类

类别关键指标说明
系统资源CPU、内存、磁盘 I/O基础资源使用情况
连接连接数、连接池客户端连接状态
操作读/写/更新/删除次数数据库操作统计
查询慢查询、扫描行数查询性能指标
复制复制延迟、oplog 大小副本集同步状态
存储数据大小、索引大小存储空间使用

MongoDB 内置监控工具

serverStatus - 服务器状态

// 获取服务器状态
db.serverStatus()

// 查看特定部分
db.serverStatus().connections // 连接信息
db.serverStatus().opcounters // 操作计数
db.serverStatus().memory // 内存使用
db.serverStatus().network // 网络统计
db.serverStatus().repl // 复制状态

连接信息解读

db.serverStatus().connections
// {
// "current" : 100, // 当前连接数
// "available" : 51100, // 可用连接数
// "totalCreated" : 5000 // 总创建连接数
// }

操作计数解读

db.serverStatus().opcounters
// {
// "insert" : 1000, // 插入次数
// "query" : 5000, // 查询次数
// "update" : 2000, // 更新次数
// "delete" : 500, // 删除次数
// "getmore" : 3000, // 获取更多数据次数
// "command" : 10000 // 命令执行次数
// }

dbStats - 数据库统计

// 查看数据库统计
db.stats()

// 指定单位(MB)
db.stats(1024 * 1024)

// 输出示例
// {
// "db" : "myapp",
// "collections" : 10, // 集合数量
// "views" : 2, // 视图数量
// "objects" : 100000, // 文档数量
// "avgObjSize" : 1024, // 平均文档大小
// "dataSize" : 102400000, // 数据大小
// "storageSize" : 204800000,// 存储大小
// "indexes" : 20, // 索引数量
// "indexSize" : 51200000 // 索引大小
// }

collectionStats - 集合统计

// 查看集合统计
db.users.stats()

// 指定单位
db.users.stats({ scale: 1024 * 1024 }) // MB

// 查看索引大小
db.users.stats().indexSizes

currentOp - 当前操作

// 查看所有正在执行的操作
db.currentOp()

// 查看运行超过 3 秒的操作
db.currentOp({ "secs_running": { "$gt": 3 } })

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

// 杀掉指定操作
db.killOp(opId)

explain - 查询分析

// 分析查询执行计划
db.users.find({ age: { $gt: 25 } }).explain()

// 详细模式
db.users.find({ age: { $gt: 25 } }).explain("executionStats")

// 最详细模式
db.users.find({ age: { $gt: 25 } }).explain("allPlansExecution")

explain 输出关键字段

{
"queryPlanner": {
"winningPlan": {
"stage": "FETCH", // 查询阶段
"inputStage": {
"stage": "IXSCAN", // 索引扫描
"indexName": "age_1", // 使用的索引
"direction": "forward" // 扫描方向
}
}
},
"executionStats": {
"executionTimeMillis": 10, // 执行时间(毫秒)
"totalDocsExamined": 1000, // 扫描文档数
"totalKeysExamined": 500, // 扫描索引键数
"nReturned": 500 // 返回文档数
}
}

慢查询分析

启用慢查询日志

// 查看当前慢查询设置
db.getProfilingStatus()
// { "was" : 1, "slowms" : 100 } // 记录超过 100ms 的查询

// 设置慢查询阈值
db.setProfilingLevel(1, 50) // 记录超过 50ms 的查询

// 记录所有查询(影响性能,仅调试时使用)
db.setProfilingLevel(2)

// 关闭慢查询记录
db.setProfilingLevel(0)

分析级别说明

级别说明
0不记录任何查询
1只记录慢查询(默认 100ms)
2记录所有查询

查询慢查询日志

// 查看慢查询集合
db.system.profile.find().sort({ ts: -1 }).limit(10)

// 查找特定集合的慢查询
db.system.profile.find({ ns: "myapp.users" }).sort({ millis: -1 })

// 分析慢查询
db.system.profile.aggregate([
{ $group: {
_id: "$ns",
count: { $sum: 1 },
avgTime: { $avg: "$millis" },
maxTime: { $max: "$millis" }
}},
{ $sort: { avgTime: -1 } }
])

查询优化策略

1. 创建合适的索引

// 分析查询计划
db.orders.find({ status: "completed", createdAt: { $gt: new Date("2024-01-01") } })
.explain("executionStats")

// 如果 COLLSCAN(全表扫描),创建索引
db.orders.createIndex({ status: 1, createdAt: -1 })

// 检查索引是否被使用
db.orders.find({ status: "completed" }).explain()
// 查看 "winningPlan.inputStage.stage" 是否为 "IXSCAN"

2. 优化查询条件顺序

// 不好的写法:先范围查询,后精确匹配
db.orders.find({ createdAt: { $gt: new Date("2024-01-01") }, status: "completed" })

// 好的写法:先精确匹配,后范围查询
// 配合索引 { status: 1, createdAt: -1 }
db.orders.find({ status: "completed", createdAt: { $gt: new Date("2024-01-01") } })

3. 使用覆盖查询

覆盖查询是指查询只需要访问索引,不需要访问文档本身:

// 创建索引
db.users.createIndex({ username: 1, email: 1 })

// 覆盖查询:只返回索引中的字段
db.users.find(
{ username: "zhangsan" },
{ _id: 0, username: 1, email: 1 } // 只返回索引字段
).explain()
// 查看 "totalDocsExamined" 是否为 0

4. 限制返回字段

// 不好的写法:返回所有字段
db.users.find({ status: "active" })

// 好的写法:只返回需要的字段
db.users.find(
{ status: "active" },
{ username: 1, email: 1, _id: 0 }
)

5. 分页优化

// 低效分页:skip 越大越慢
db.orders.find().skip(10000).limit(10)

// 优化方案1:使用范围查询
// 记住上一页最后一个 _id
var lastId = ObjectId("...")
db.orders.find({ _id: { $gt: lastId } }).limit(10)

// 优化方案2:使用索引
db.orders.find({ status: "completed" })
.sort({ _id: 1 })
.skip(10000)
.limit(10)
// 确保有 { status: 1, _id: 1 } 索引

6. 批量操作优化

// 低效:逐条插入
for (let i = 0; i < 10000; i++) {
db.users.insertOne({ name: `user${i}` })
}

// 高效:批量插入
var batch = []
for (let i = 0; i < 10000; i++) {
batch.push({ name: `user${i}` })
if (batch.length === 1000) {
db.users.insertMany(batch)
batch = []
}
}
if (batch.length > 0) {
db.users.insertMany(batch)
}

// 更高效:使用 bulkWrite
db.users.bulkWrite(
Array.from({ length: 10000 }, (_, i) => ({
insertOne: { document: { name: `user${i}` } }
}))
)

内存优化

WiredTiger 缓存

MongoDB 使用 WiredTiger 存储引擎,默认使用 50% 的系统内存作为缓存:

// 查看缓存状态
db.serverStatus().wiredTiger.cache

// 关键指标
// "bytes currently in the cache" - 缓存中的数据量
// "maximum bytes configured" - 配置的最大缓存
// "pages read into cache" - 从磁盘读入缓存的页数
// "pages written from cache" - 从缓存写入磁盘的页数

内存配置

# mongod.conf
storage:
wiredTiger:
engineConfig:
cacheSizeGB: 4 # 设置缓存大小为 4GB

内存优化建议

  1. 确保缓存足够:缓存应能容纳热数据
  2. 监控缓存命中率:高命中率意味着内存足够
  3. 避免内存碎片:定期 compact 集合
// 计算缓存命中率
var stats = db.serverStatus().wiredTiger.cache
var hitRate = stats["pages requested from the cache"] /
(stats["pages requested from the cache"] + stats["pages read into cache"])
print(`Cache hit rate: ${(hitRate * 100).toFixed(2)}%`)

磁盘 I/O 优化

存储配置

# mongod.conf
storage:
dbPath: /data/db
journal:
enabled: true
wiredTiger:
engineConfig:
journalCompressor: snappy # 压缩日志
collectionConfig:
blockCompressor: snappy # 压缩数据

磁盘监控

// 查看磁盘使用
db.stats()
db.users.stats()

// 监控磁盘 I/O
db.serverStatus().wiredTiger["block-manager"]

I/O 优化建议

  1. 使用 SSD:随机读写性能远超 HDD
  2. 分离数据和日志:将 journal 放在单独磁盘
  3. 适当预分配:避免频繁文件扩展
  4. 定期 compact:减少碎片
// 压缩集合(减少碎片)
db.runCommand({ compact: "users" })

连接池优化

连接池配置

// Node.js 驱动连接池配置
const client = new MongoClient(uri, {
maxPoolSize: 100, // 最大连接数
minPoolSize: 10, // 最小连接数
maxIdleTimeMS: 30000, // 空闲连接超时
waitQueueTimeoutMS: 5000 // 等待连接超时
})

连接数监控

// 查看当前连接数
db.serverStatus().connections

// 查看连接来源
db.currentOp(true).inprog.forEach(function(op) {
if (op.client) print(op.client)
})

连接数优化建议

  1. 合理设置连接池大小:通常 100-500
  2. 复用连接:应用层复用 MongoClient
  3. 监控连接泄漏:连接数持续增长可能是泄漏

监控工具推荐

命令行工具

# mongostat - 实时统计
mongostat --host localhost --port 27017 -u admin -p password --authenticationDatabase admin

# 输出示例
# insert query update delete getmore command dirty used flushes vsize res qrw arw net_in net_out conn
# *0 *0 *0 *0 0 2|0 0.0% 0.0% 0 1.5G 62M 0|0 1|0 158b 44.1k 2

# mongotop - 读写时间统计
mongotop --host localhost --port 27017

# 输出示例
# ns total read write
# myapp.users 15ms 15ms 0ms
# myapp.orders 10ms 10ms 0ms

MongoDB Atlas 监控

Atlas 提供丰富的可视化监控:

  • 实时性能图表
  • 慢查询分析
  • 自动建议优化
  • 告警配置

第三方监控工具

  • Prometheus + Grafana:开源监控方案
  • Datadog:商业 APM 工具
  • New Relic:应用性能监控
  • Percona PMM:MongoDB 专用监控

性能优化清单

日常检查项

检查项命令期望结果
连接数db.serverStatus().connectionscurrent < 80% max
缓存命中率db.serverStatus().wiredTiger.cache> 95%
慢查询数量db.system.profile.count()很少或无
复制延迟rs.status()< 10s
磁盘空间db.stats()< 80% 使用

小结

本章我们学习了:

  1. 监控指标:系统资源、连接、操作、查询、复制、存储
  2. 内置工具:serverStatus、dbStats、currentOp、explain
  3. 慢查询分析:profiling 配置和分析方法
  4. 查询优化:索引、覆盖查询、分页优化
  5. 内存优化:WiredTiger 缓存配置
  6. 磁盘优化:存储配置和 compact
  7. 连接池优化:连接池配置和监控
  8. 监控工具:mongostat、mongotop、Atlas 监控

性能优化是一个持续的过程,需要定期监控、分析瓶颈、优化调整。