性能监控与调优
性能优化是 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
内存优化建议
- 确保缓存足够:缓存应能容纳热数据
- 监控缓存命中率:高命中率意味着内存足够
- 避免内存碎片:定期 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 优化建议
- 使用 SSD:随机读写性能远超 HDD
- 分离数据和日志:将 journal 放在单独磁盘
- 适当预分配:避免频繁文件扩展
- 定期 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)
})
连接数优化建议
- 合理设置连接池大小:通常 100-500
- 复用连接:应用层复用 MongoClient
- 监控连接泄漏:连接数持续增长可能是泄漏
监控工具推荐
命令行工具
# 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().connections | current < 80% max |
| 缓存命中率 | db.serverStatus().wiredTiger.cache | > 95% |
| 慢查询数量 | db.system.profile.count() | 很少或无 |
| 复制延迟 | rs.status() | < 10s |
| 磁盘空间 | db.stats() | < 80% 使用 |
小结
本章我们学习了:
- 监控指标:系统资源、连接、操作、查询、复制、存储
- 内置工具:serverStatus、dbStats、currentOp、explain
- 慢查询分析:profiling 配置和分析方法
- 查询优化:索引、覆盖查询、分页优化
- 内存优化:WiredTiger 缓存配置
- 磁盘优化:存储配置和 compact
- 连接池优化:连接池配置和监控
- 监控工具:mongostat、mongotop、Atlas 监控
性能优化是一个持续的过程,需要定期监控、分析瓶颈、优化调整。