第14课: 性能优化
explain() - 查询分析
// 查看查询执行计划
db.users.find({ age: 25 }).explain("executionStats")
// 三种模式:
// - queryPlanner: 只显示查询计划
// - executionStats: 显示执行统计(推荐)
// - allPlansExecution: 显示所有候选计划
关键性能指标
// 分析explain()输出
{
"executionStats": {
"executionTimeMillis": 15, // 执行时间(毫秒)
"totalDocsExamined": 10000, // 扫描的文档数
"totalKeysExamined": 10000, // 扫描的索引键数
"nReturned": 50, // 返回的文档数
"executionStages": {
"stage": "IXSCAN", // IXSCAN=索引扫描,COLLSCAN=全表扫描
"indexName": "age_1"
}
}
}
// 理想情况:
// - totalKeysExamined ≈ nReturned
// - stage = "IXSCAN"
// - executionTimeMillis < 100ms
索引优化
// 1. 为查询字段创建索引
db.users.createIndex({ email: 1 })
// 2. 复合索引遵循ESR规则
// E (Equality): 等值查询字段
// S (Sort): 排序字段
// R (Range): 范围查询字段
db.orders.createIndex({ status: 1, createdAt: -1, amount: 1 })
// 3. 覆盖索引(Covered Query)
// 查询的所有字段都在索引中
db.users.find(
{ age: 25 },
{ age: 1, name: 1, _id: 0 }
).hint({ age: 1, name: 1 })
// 4. 删除未使用的索引
db.users.dropIndex("unused_index_1")
查询优化技巧
// 1. 使用投影减少数据传输
db.users.find({}, { name: 1, email: 1 })
// 2. 限制返回数量
db.users.find().limit(100)
// 3. 使用$exists检查字段存在性
db.users.find({ email: { $exists: true } })
// 4. 避免$where和$regex(性能差)
// 差:
db.users.find({ $where: "this.age > 18" })
// 好:
db.users.find({ age: { $gt: 18 } })
// 5. 使用$in代替多个$or
// 差:
db.users.find({ $or: [{ age: 20 }, { age: 25 }, { age: 30 }] })
// 好:
db.users.find({ age: { $in: [20, 25, 30] } })
慢查询日志
// 启用慢查询日志(超过100ms)
db.setProfilingLevel(1, { slowms: 100 })
// 查看慢查询
db.system.profile.find().sort({ ts: -1 }).limit(10)
// 分析慢查询
db.system.profile.find({
millis: { $gt: 100 }
}).sort({ millis: -1 })
// 关闭慢查询日志
db.setProfilingLevel(0)
聚合优化
// 1. 尽早使用$match过滤
db.orders.aggregate([
{ $match: { status: "completed" } }, // 先过滤
{ $group: { _id: "$userId", total: { $sum: "$amount" } } }
])
// 2. 尽早使用$project减少字段
db.orders.aggregate([
{ $project: { userId: 1, amount: 1 } }, // 只保留需要的字段
{ $group: { _id: "$userId", total: { $sum: "$amount" } } }
])
// 3. 使用$limit减少处理数据量
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $sort: { amount: -1 } },
{ $limit: 100 }
])
// 4. 使用allowDiskUse处理大数据集
db.orders.aggregate(
[...],
{ allowDiskUse: true }
)
连接池优化
// Node.js驱动配置
const client = new MongoClient(uri, {
maxPoolSize: 50, // 最大连接数
minPoolSize: 10, // 最小连接数
maxIdleTimeMS: 30000, // 空闲连接超时
waitQueueTimeoutMS: 5000 // 等待连接超时
});
批量操作优化
// 使用bulkWrite批量写入
db.users.bulkWrite([
{ insertOne: { document: { name: "用户1" } } },
{ updateOne: { filter: { _id: 1 }, update: { $set: { age: 26 } } } },
{ deleteOne: { filter: { _id: 2 } } }
], { ordered: false }) // 无序执行更快
// 批量插入
db.users.insertMany(
[...1000个文档...],
{ ordered: false }
)
内存优化
// 1. 工作集(Working Set)应该小于RAM
// 查看工作集大小
db.serverStatus().wiredTiger.cache
// 2. 配置WiredTiger缓存大小
// 默认:(RAM - 1GB) / 2 或 256MB(取较大值)
mongod --wiredTigerCacheSizeGB 4
// 3. 监控内存使用
db.serverStatus().mem
磁盘I/O优化
// 1. 使用SSD存储
// 2. 分离数据和日志到不同磁盘
mongod --dbpath /data/db --logpath /logs/mongodb.log
// 3. 启用压缩
mongod --wiredTigerCollectionBlockCompressor snappy
// 4. 调整日志提交间隔
mongod --wiredTigerJournalCompressor snappy
读写分离
// 将读操作分散到从节点
db.users.find().readPref("secondaryPreferred")
// 适用场景:
// - 读多写少
// - 可以容忍轻微的数据延迟
// - 减轻主节点压力
数据建模优化
// 1. 嵌入式文档减少关联查询
{
orderId: "order123",
user: {
id: "user123",
name: "张三",
email: "zhangsan@example.com"
},
items: [...]
}
// 2. 使用桶模式聚合时序数据
{
sensorId: "sensor001",
hour: ISODate("2026-02-03T10:00:00Z"),
measurements: [
{ minute: 0, temp: 25.5 },
{ minute: 1, temp: 25.6 }
// ... 60条数据
]
}
// 3. 使用属性模式处理稀疏字段
{
productId: "prod123",
attributes: [
{ key: "color", value: "red" },
{ key: "size", value: "L" }
]
}
监控工具
// 1. mongostat - 实时统计
mongostat --host localhost:27017
// 2. mongotop - 查看集合读写时间
mongotop --host localhost:27017
// 3. 查看当前操作
db.currentOp()
// 4. 终止慢查询
db.killOp(opid)
// 5. 服务器状态
db.serverStatus()
// 6. 数据库统计
db.stats()
// 7. 集合统计
db.users.stats()
性能测试
// 使用mongoperf测试磁盘性能
mongoperf < test.json
// test.json内容
{
nThreads: 16,
fileSizeMB: 1000,
sleepMicros: 0,
mmf: true,
r: true,
w: true
}
常见性能问题
| 问题 |
原因 |
解决方案 |
| 查询慢 |
缺少索引 |
创建合适的索引 |
| 写入慢 |
索引过多 |
删除不必要的索引 |
| 内存不足 |
工作集过大 |
增加RAM或分片 |
| 磁盘I/O高 |
频繁读写 |
使用SSD,优化查询 |
| 连接数过多 |
连接池配置不当 |
调整连接池大小 |
性能优化清单
- 为高频查询字段创建索引
- 使用explain()分析查询
- 启用慢查询日志
- 定期分析和优化索引
- 使用投影减少数据传输
- 批量操作代替单条操作
- 合理设计数据模型
- 配置合适的连接池
- 监控服务器资源使用
- 使用副本集实现读写分离
性能优化原则:
- 先测量,后优化
- 优化最慢的查询
- 索引不是越多越好
- 数据建模比索引更重要
- 定期监控和调优
练习题:
- 使用explain()分析一个慢查询并优化
- 启用慢查询日志并分析最慢的10个查询
- 为高频查询创建覆盖索引
- 优化一个聚合管道的性能
- 使用mongostat监控数据库性能
- 实现批量写入优化