文档中心>云数据库 MongoDB>开发规范>性能优化与问题排查

性能优化与问题排查

最近更新时间:2026-06-10 16:12:00

我的收藏
本文介绍腾讯云 MongoDB 的性能优化方法和常见问题排查指南,包括性能分析工具使用、常见性能问题诊断、开发阶段检查清单等内容,帮助开发者在开发和运维过程中快速定位并解决性能问题。

性能问题场景

在 MongoDB 的生产实践中,性能优化是保障系统稳定性的核心环节。随着业务体量增长,开发者必须具备快速定位与排查性能瓶颈的能力。常见的性能异常指标包括:
查询延迟激增:慢查询高发,响应时间从毫秒级恶化至秒级甚至分钟级。
CPU 持续高负载:数据库实例 CPU 占用率高居不下,引发系统响应断崖式下跌。
内存承压:WiredTiger 引擎缓存命中率下降,淘汰(Eviction)机制频繁触发,导致查询抖动。
磁盘 I/O 遭遇瓶颈:读写吞吐达到物理上限,操作在队列中严重积压。
连接数过载/耗尽:连接池满载,导致新请求无法建立连接而被拒绝。
本文档将系统性地介绍如何借助性能分析工具,识别并高效解决上述核心性能痛点。

性能分析工具

explain()执行计划分析

explain()是 MongoDB 最重要的性能分析工具,用于分析查询的执行计划和性能指标。explain() 的三种模式:
模式
说明
使用场景
queryPlanner(默认)
仅返回查询计划,不执行查询
快速查看优化器选择的计划
executionStats
执行查询并返回统计信息
分析查询实际性能
allPlansExecution
执行所有候选计划并返回信息
深入分析优化器决策

基本使用

// 查询计划
db.t_orders.find({ status: "paid" }).explain()

// 执行统计
db.t_orders.find({ status: "paid" }).explain("executionStats")

// 所有计划执行信息
db.t_orders.find({ status: "paid" }).explain("allPlansExecution")

关键指标解读

{
"queryPlanner": {
"winningPlan": {
"stage": "FETCH", // 执行阶段
"inputStage": {
"stage": "IXSCAN", // 使用索引扫描
"keyPattern": { "status": 1 },
"indexName": "idx_status",
"direction": "forward"
}
},
"rejectedPlans": [...] // 被拒绝的计划
},
"executionStats": {
"executionSuccess": true,
"nReturned": 1000, // 返回文档数
"executionTimeMillis": 15, // 执行时间(毫秒)
"totalKeysExamined": 1000, // 扫描的索引条目数
"totalDocsExamined": 1000, // 扫描的文档数
"executionStages": {
"stage": "FETCH",
"nReturned": 1000,
"docsExamined": 1000,
"inputStage": {
"stage": "IXSCAN",
"nReturned": 1000,
"keysExamined": 1000
}
}
}
}

执行阶段(Stage)说明

Stage
说明
是否理想
COLLSCAN
全表扫描
需要创建索引
IXSCAN
索引扫描
理想
FETCH
根据索引回表获取文档
正常
SORT
内存排序
需要为排序字段创建索引
SORT_KEY_GENERATOR
生成排序键
配合 SORT 出现
PROJECTION_COVERED
覆盖索引投影
理想(无需回表)
PROJECTION_SIMPLE
简单投影
正常
LIMIT
限制返回数量
正常
SKIP
跳过文档
正常(深度分页时低效)
SHARD_MERGE
分片结果合并
正常(分片集群)
SINGLE_SHARD
单分片查询
定向查询

性能指标评估标准

指标
理想值
需优化
totalDocsExamined / nReturned
≈ 1
> 10(扫描过多文档)
totalKeysExamined / nReturned
≈ 1
> 10(索引效率低)
executionTimeMillis
< 100ms
> 1000ms(需优化)
stage
IXSCAN
COLLSCAN(缺少索引)
是否有 SORT 阶段
有(内存排序)

慢查询日志分析

MongoDB 会自动记录执行时间超过阈值的慢查询,是性能问题排查的重要依据。

慢查询配置

// 查看当前慢查询阈值
db.getProfilingStatus()
// { "was": 1, "slowms": 100 }

// 设置慢查询阈值为200ms
db.setProfilingLevel(1, { slowms: 200 })

// level 0: 关闭
// level 1: 仅记录慢查询
// level 2: 记录所有操作(慎用,性能影响大)

查询慢查询日志

您可以登录控制台,在 慢日志查询 页面直接检索并分析慢日志。具体操作,请参见 慢日志查询

currentOp 监控当前操作

currentOp 能够实时捕获数据库当前正在执行的所有活动操作快照,适合排查当前性能问题。
// 查看所有当前操作
db.currentOp()

// 查看执行时间超过5秒的操作
db.currentOp({
"active": true,
"secs_running": { "$gt": 5 }
})

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

// 查看特定集合的操作
db.currentOp({
"ns": "mydb.t_orders"
})


// 终止执行时间超过10秒的操作
db.currentOp().inprog.forEach(function(item){if(item.secs_running > 10 )db.killOp(item.opid)})

serverStatus 监控指标

serverStatus 用于获取 MongoDB 实例全局状态快照与计数器信息。
db.serverStatus()

// 关注的关键指标
{
"connections": {
"current": 50, // 当前连接数
"available": 950, // 可用连接数
"totalCreated": 1000
},
"opcounters": {
"insert": 10000,
"query": 50000,
"update": 5000,
"delete": 1000
},
"mem": {
"resident": 4096, // 常驻内存(MB)
"virtual": 8192 // 虚拟内存(MB)
},
"wiredTiger": {
"cache": {
"bytes currently in the cache": ...,
"maximum bytes configured": ...
}
}
}

常见性能问题诊断

全表扫描(COLLSCAN)

问题表现

执行 explain() 分析查询计划时,stage 显示为 "COLLSCAN"。
查询耗时与集合中的数据量呈线性增长关系。
CPU 利用率和磁盘 I/O 负载显著上升。

排查步骤

// 1. 确认是否全表扫描
db.t_orders.find({ status: "paid" }).explain("executionStats")
// 检查 winningPlan.stage 是否为 COLLSCAN

// 2. 检查现有索引
db.t_orders.getIndexes()

// 3. 创建合适的索引
db.t_orders.createIndex({ status: 1 }, { background: true })

// 4. 验证索引生效
db.t_orders.find({ status: "paid" }).explain("executionStats")
// 确认 stage 变为 IXSCAN

常见原因

原因
解决方案
缺少索引
为查询条件字段创建索引
查询条件不匹配索引
调整查询或创建合适的复合索引
使用了无法利用索引的操作符
避免使用$ne$nin、无前缀正则等
索引选择性低
创建复合索引提高选择性

内存排序超限

问题表现

数据库返回错误:Sort operation used more than the maximum 33554432 bytes of RAM
执行 explain() 分析查询计划时,显示存在 "SORT" 阶段。

排查步骤

// 1. 确认是否内存排序
db.t_orders.find({ status: "paid" }).sort({ createTime: -1 }).explain("executionStats")
// 检查是否有 SORT 阶段

// 2. 创建支持排序的索引
db.t_orders.createIndex({ status: 1, createTime: -1 }, { background: true })

// 3. 验证索引排序
db.t_orders.find({ status: "paid" }).sort({ createTime: -1 }).explain("executionStats")
// 确认无 SORT 阶段

解决方案

方案
说明
创建索引(推荐)
为排序字段创建索引,索引排序无内存限制。
减少排序数据量
增加筛选条件,减少需要排序的文档数。
分页优化
使用基于排序字段的游标分页替代深度分页 skip+sort。具体信息,请参见 避免深度分页
临时方案
使用 allowDiskUse(性能较差)。

热点数据与热点分片

问题表现

分片集群中,某个特定分片的 CPU、I/O 负载或存储空间占用远高于其他分片。
写入流量(QPS)集中在单个分片,其他分片处于闲置状态。
集群中 Chunk 迁移频繁,但各分片间的数据不均衡状态未能得到有效改善。

排查步骤

// 1. 查看分片数据分布
db.t_orders.getShardDistribution()

// 2. 查看chunk分布
db.getSiblingDB("config").chunks.aggregate([
{ $match: { ns: "mydb.t_orders" } },
{ $group: { _id: "$shard", count: { $sum: 1 } } }
])

// 3. 检查分片键选择
db.getSiblingDB("config").collections.findOne({ _id: "mydb.t_orders" })

常见原因与解决方案

原因
解决方案
分片键是单调递增字段
使用 Hash 分片或复合分片键
分片键基数太低
选择高基数字段或复合键
写入集中在最新数据
使用时间戳 Hash 分片
某些热点值访问频繁
业务层缓存热点数据

连接数耗尽

问题表现
客户端或应用日志抛出错误:connection pool exhausted
数据库实例拒绝新的客户端连接请求,导致新连接建立失败。
应用端的大量数据库请求因处于等待连接状态而触发超时。
排查步骤
// 1. 查看当前连接数
db.serverStatus().connections
// { "current": 950, "available": 50, "totalCreated": 10000 }

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

// 3. 检查应用连接池配置
// 确认maxPoolSize设置是否合理
解决方案
方案
说明
优化连接池
减少单应用的 maxPoolSize
检查连接泄漏
确保连接正确释放
增加实例规格
提升实例支持的最大连接数
减少应用实例
降低总连接需求

锁等待与死锁

问题表现

数据库整体或部分特定操作的响应时间出现大幅波动,表现不稳定。
执行 currentOp() 时,结果中存在大量操作处于 waitingForLock: true 的等待状态。
某些读写操作长时间处于挂起状态,无法及时返回执行结果。

排查步骤

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

// 2. 查看持有锁时间长的操作
db.currentOp({
"active": true,
"secs_running": { "$gt": 10 }
})

// 3. 查看锁状态
db.serverStatus().locks

解决方案

原因
解决方案
长事务
缩短事务执行时间,拆分大事务
长时间运行的查询
优化查询,添加索引
前台建索引
使用 background:true
DDL 操作
安排在低峰期执行

开发阶段检查清单

上线前 SQL 审核检查项

查询语句检查

检查项
验证方法
通过标准
查询使用索引
explain("executionStats")
stage 为 IXSCAN
无内存排序
explain("executionStats")
无 SORT 阶段
扫描效率
totalDocsExamined/nReturned
比值接近1
返回字段最小化
检查是否使用投影
仅返回必要字段
分页方式合理
检查分页实现
使用基于排序字段的游标分页

写入语句检查

检查项
验证方法
通过标准
Update 带条件
代码审查
禁止空条件更新
使用$set 更新
代码审查
禁止全文档替换
批量操作有控制
代码审查
单批次≤1000条
Write Concern 合理
代码审查
核心业务使用 majority

索引覆盖检查

检查现有索引是否满足查询需求

// 列出所有索引
db.t_orders.getIndexes()

// 检查索引使用统计
db.t_orders.aggregate([{ $indexStats: {} }])

// 针对主要查询验证索引覆盖
const mainQueries = [
{ status: "paid" },
{ customerId: "C001", createTime: { $gte: ISODate("2024-01-01") } },
{ orderId: "ORD001" }
];

mainQueries.forEach(query => {
const plan = db.t_orders.find(query).explain("executionStats");
print("Query:", JSON.stringify(query));
print("Stage:", plan.queryPlanner.winningPlan.stage);
print("DocsExamined:", plan.executionStats.totalDocsExamined);
print("---");
});