执行计划(explain)与查询性能分析
执行计划(explain)基础概念
MongoDB的explain()
方法是一个强大的诊断工具,它能够展示查询的执行细节。通过在查询语句后附加.explain()
,可以获取查询优化器选择的执行计划。执行计划包含了查询如何被处理的关键信息,包括索引使用情况、扫描文档数量、执行时间等。
// 基本使用示例
db.collection.find({ name: "张三" }).explain()
执行计划有三种详细模式:
queryPlanner
(默认):仅显示查询优化器选择的计划executionStats
:包含实际执行统计信息allPlansExecution
:显示所有候选计划的完整信息
理解执行计划输出
执行计划输出的核心部分包含以下关键信息:
-
queryPlanner:展示优化器选择的查询计划
winningPlan
:被选中的执行计划rejectedPlans
:被拒绝的候选计划
-
executionStats(当使用该模式时):
executionTimeMillis
:总执行时间totalKeysExamined
:检查的索引键数量totalDocsExamined
:检查的文档数量nReturned
:返回的文档数量
// 获取详细执行统计的示例
db.orders.find({ status: "shipped" }).explain("executionStats")
索引使用分析
索引是查询性能的关键因素。通过执行计划可以确认查询是否有效使用了索引:
- IXSCAN:表示使用了索引扫描,这是理想情况
- COLLSCAN:表示进行了全集合扫描,通常性能较差
// 创建索引示例
db.products.createIndex({ category: 1, price: -1 })
// 检查索引使用
db.products.find({
category: "electronics",
price: { $gt: 500 }
}).explain("executionStats")
当看到IXSCAN
阶段时,可以查看indexName
确认具体使用了哪个索引。如果发现COLLSCAN
,应考虑创建适当的索引。
查询性能关键指标
分析执行计划时,应特别关注以下指标:
-
查询选择性:
nReturned / totalDocsExamined
比值越高越好- 理想情况下,这个比值应接近1
-
内存使用:
works
字段表示操作的工作量advanced
表示提前返回的文档数
-
阶段执行顺序:
- MongoDB执行计划是一个阶段树
- 子阶段先执行,结果传递给父阶段
// 复合查询示例
db.users.find({
age: { $gt: 30 },
city: "北京",
lastLogin: { $gt: new Date("2023-01-01") }
}).explain("executionStats")
常见性能问题诊断
-
全集合扫描(COLLSCAN):
- 症状:
totalDocsExamined
接近集合总文档数 - 解决方案:为查询条件创建适当索引
- 症状:
-
低效索引使用:
- 症状:
totalKeysExamined
远大于nReturned
- 解决方案:优化索引或查询条件
- 症状:
-
内存排序(SORT):
- 症状:出现
SORT
阶段且memLimit
被超过 - 解决方案:为排序字段创建索引
- 症状:出现
// 内存排序问题示例
db.employees.find({ department: "IT" })
.sort({ salary: -1 })
.explain("executionStats")
高级执行计划分析技巧
-
索引交集:
- MongoDB可以使用多个索引的交集
- 执行计划中会出现
AND_SORTED
或AND_HASH
阶段
-
覆盖查询:
- 当查询只需要索引字段时,可以完全避免读取文档
- 检查
indexOnly
字段是否为true
// 覆盖查询示例
db.customers.createIndex({ email: 1, name: 1 })
db.customers.find({ email: "user@example.com" }, { _id: 0, name: 1 })
.explain("executionStats")
- 查询形状分析:
- 相同"形状"的查询会重用查询计划
- 使用
$planCacheStats
查看计划缓存
实际案例分析
案例1:电子商务网站的产品搜索
// 原始查询
db.products.find({
category: "electronics",
price: { $lte: 1000 },
rating: { $gte: 4 },
inStock: true
}).sort({ price: 1 }).explain("executionStats")
// 优化方案:创建复合索引
db.products.createIndex({
category: 1,
inStock: 1,
rating: 1,
price: 1
})
案例2:社交媒体应用的用户动态查询
// 分页查询性能问题
db.posts.find({ userId: "user123" })
.sort({ createdAt: -1 })
.skip(100)
.limit(10)
.explain("executionStats")
// 优化方案:使用范围查询代替skip
const lastDate = getLastDisplayedPostDate() // 从前端获取
db.posts.find({
userId: "user123",
createdAt: { $lt: lastDate }
})
.sort({ createdAt: -1 })
.limit(10)
执行计划与分片集群
在分片环境中,执行计划分析更加复杂:
- mergeSort:来自不同分片的结果需要合并排序
- shardFilter:过滤掉不符合分片键范围的数据
- clusterTime:协调操作的时间开销
// 分片集群查询示例
db.shardedCollection.find({
region: "APAC",
value: { $gt: 10000 }
}).explain("executionStats")
应特别注意shards
字段,它显示了每个分片的执行统计。不均衡的分片负载可能表明需要调整分片策略。
查询优化实践建议
-
索引策略:
- 遵循ESR规则(等值-排序-范围)
- 定期审查和删除未使用的索引
-
查询重构:
- 避免使用
$where
和$exists
- 限制返回字段数量
- 避免使用
-
监控工具:
- 使用
db.currentOp()
监控运行中的查询 - 配置慢查询日志
- 使用
// 设置慢查询阈值(单位:毫秒)
db.setProfilingLevel(1, { slowms: 100 })
// 查看分析结果
db.system.profile.find().sort({ ts: -1 }).limit(10)
执行计划与聚合管道
聚合管道的执行计划分析更为复杂,因为每个阶段都可能改变数据流:
db.orders.aggregate([
{ $match: { status: "completed", date: { $gt: new Date("2023-01-01") } } },
{ $group: { _id: "$productId", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } },
{ $limit: 10 }
]).explain("executionStats")
关键观察点:
- 管道优化标志(如
$match
提前) - 每个阶段的输入/输出文档数
- 内存使用情况(特别是
$group
和$sort
阶段)
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:索引的创建、查看与删除
下一篇:索引优化策略与常见问题