阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 执行计划(explain)与查询性能分析

执行计划(explain)与查询性能分析

作者:陈川 阅读数:39026人阅读 分类: MongoDB

执行计划(explain)基础概念

MongoDB的explain()方法是一个强大的诊断工具,它能够展示查询的执行细节。通过在查询语句后附加.explain(),可以获取查询优化器选择的执行计划。执行计划包含了查询如何被处理的关键信息,包括索引使用情况、扫描文档数量、执行时间等。

// 基本使用示例
db.collection.find({ name: "张三" }).explain()

执行计划有三种详细模式:

  • queryPlanner(默认):仅显示查询优化器选择的计划
  • executionStats:包含实际执行统计信息
  • allPlansExecution:显示所有候选计划的完整信息

理解执行计划输出

执行计划输出的核心部分包含以下关键信息:

  1. queryPlanner:展示优化器选择的查询计划

    • winningPlan:被选中的执行计划
    • rejectedPlans:被拒绝的候选计划
  2. executionStats(当使用该模式时):

    • executionTimeMillis:总执行时间
    • totalKeysExamined:检查的索引键数量
    • totalDocsExamined:检查的文档数量
    • nReturned:返回的文档数量
// 获取详细执行统计的示例
db.orders.find({ status: "shipped" }).explain("executionStats")

索引使用分析

索引是查询性能的关键因素。通过执行计划可以确认查询是否有效使用了索引:

  1. IXSCAN:表示使用了索引扫描,这是理想情况
  2. COLLSCAN:表示进行了全集合扫描,通常性能较差
// 创建索引示例
db.products.createIndex({ category: 1, price: -1 })

// 检查索引使用
db.products.find({ 
  category: "electronics", 
  price: { $gt: 500 } 
}).explain("executionStats")

当看到IXSCAN阶段时,可以查看indexName确认具体使用了哪个索引。如果发现COLLSCAN,应考虑创建适当的索引。

查询性能关键指标

分析执行计划时,应特别关注以下指标:

  1. 查询选择性

    • nReturned / totalDocsExamined比值越高越好
    • 理想情况下,这个比值应接近1
  2. 内存使用

    • works字段表示操作的工作量
    • advanced表示提前返回的文档数
  3. 阶段执行顺序

    • MongoDB执行计划是一个阶段树
    • 子阶段先执行,结果传递给父阶段
// 复合查询示例
db.users.find({
  age: { $gt: 30 },
  city: "北京",
  lastLogin: { $gt: new Date("2023-01-01") }
}).explain("executionStats")

常见性能问题诊断

  1. 全集合扫描(COLLSCAN)

    • 症状:totalDocsExamined接近集合总文档数
    • 解决方案:为查询条件创建适当索引
  2. 低效索引使用

    • 症状:totalKeysExamined远大于nReturned
    • 解决方案:优化索引或查询条件
  3. 内存排序(SORT)

    • 症状:出现SORT阶段且memLimit被超过
    • 解决方案:为排序字段创建索引
// 内存排序问题示例
db.employees.find({ department: "IT" })
  .sort({ salary: -1 })
  .explain("executionStats")

高级执行计划分析技巧

  1. 索引交集

    • MongoDB可以使用多个索引的交集
    • 执行计划中会出现AND_SORTEDAND_HASH阶段
  2. 覆盖查询

    • 当查询只需要索引字段时,可以完全避免读取文档
    • 检查indexOnly字段是否为true
// 覆盖查询示例
db.customers.createIndex({ email: 1, name: 1 })
db.customers.find({ email: "user@example.com" }, { _id: 0, name: 1 })
  .explain("executionStats")
  1. 查询形状分析
    • 相同"形状"的查询会重用查询计划
    • 使用$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)

执行计划与分片集群

在分片环境中,执行计划分析更加复杂:

  1. mergeSort:来自不同分片的结果需要合并排序
  2. shardFilter:过滤掉不符合分片键范围的数据
  3. clusterTime:协调操作的时间开销
// 分片集群查询示例
db.shardedCollection.find({ 
  region: "APAC",
  value: { $gt: 10000 } 
}).explain("executionStats")

应特别注意shards字段,它显示了每个分片的执行统计。不均衡的分片负载可能表明需要调整分片策略。

查询优化实践建议

  1. 索引策略

    • 遵循ESR规则(等值-排序-范围)
    • 定期审查和删除未使用的索引
  2. 查询重构

    • 避免使用$where$exists
    • 限制返回字段数量
  3. 监控工具

    • 使用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")

关键观察点:

  1. 管道优化标志(如$match提前)
  2. 每个阶段的输入/输出文档数
  3. 内存使用情况(特别是$group$sort阶段)

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌