读取 Azure DocumentDB 中的查询解释输出

explain()命令演示如何Azure DocumentDB 执行查询。 用于 explain() 确定查询是否使用索引、扫描的文档数以及出现性能瓶颈的位置。

运行说明

在任何findaggregate命令中,将explain()附加到查询后面即可运行。

db.collection.find({ field: "value" }).explain("executionStats")

使用三种详细程度模式中的一种:

Mode 说明
"queryPlanner" 返回最优查询计划,而不运行该查询。
"executionStats" 运行查询并返回获胜计划的执行统计信息。
"allPlansExecution" 运行查询并返回所有候选计划的统计信息。

对于性能分析,请使用 "executionStats" 作为起点。

了解输出结构

解释输出分为两个主要部分。

queryPlanner

queryPlanner 部分介绍查询引擎在运行查询之前如何计划执行查询。

"queryPlanner": {
  "winningPlan": {
    "stage": "FETCH",
    "inputStage": {
      "stage": "IXSCAN",
      "indexFilterSet": [
        { "$eq": { "status": "1" } }
      ],
      "indexName": "status_1"
    }
  }
}

queryPlanner 中的关键字段:

领域 说明
winningPlan 优化器选择的查询计划。
winningPlan.stage 执行阶段。 常见值为COLLSCANIXSCANFETCH
winningPlan.inputStage 向当前阶段输入数据的子阶段。
rejectedPlans 优化器未选择的其他候选计划。

executionStats

executionStats 部分包含实际查询执行中的指标。

"executionStats": {
  "nReturned": 10,
  "totalKeysExamined": 10,
  "totalDocsExamined": 10,
  "executionTimeMillis": 3
}

executionStats 中的关键字段:

领域 说明
nReturned 返回给客户端的文档数。
totalKeysExamined 扫描的索引条目数。
totalDocsExamined 从集合中扫描的文档数。
executionTimeMillis 执行查询的总时间(以毫秒为单位)。

确定执行阶段

每个 winningPlan 阶段表示查询管道中的操作。 识别各个阶段,以了解数据如何流经执行计划。

阶段 说明
COLLSCAN 完整集合扫描。 引擎读取所有文档。 如果不存在合适的索引,则会出现此阶段。
IXSCAN 索引扫描。 引擎读取索引条目以查找匹配的文档。
FETCH 文档提取。 引擎在扫描索引后从存储加载完整文档。
SORT 内存排序 如果没有索引支持排序顺序,则会出现此阶段。
COUNT_SCAN 使用索引计数。 此阶段可避免加载文档以满足计数操作。
COUNT 内存计数。 引擎在从集合中加载文档后统计文档数量。
GROUP 内存中分组操作,通常由 $group 聚合阶段生成。
LIMIT 限制传递到下一阶段的文档数。
DISTINCT_SCAN 扫描索引以返回字段的非重复值,跳过重复索引条目。
DISTINCT_UNWIND 在非重复扫描期间展开数组值,以单独枚举每个元素。
UNIQUE 重复数据删除输入阶段中的文档,用于不同的操作。
PARALLEL_MERGE 将多个并行执行分支的结果合并到单个流中。

检测性能问题

使用以下模式确定解释输出中的常见性能问题。

集合扫描而不是索引扫描

阶段 COLLSCAN 意味着查询读取集合中的每个文档。 对于大型集合,此操作成本高昂。

指标:winningPlan.stage"COLLSCAN".

"winningPlan": {
  "stage": "COLLSCAN"
}

行动: 在查询筛选器中使用的字段上创建索引。 如果查询筛选多个字段、对结果进行排序或使用聚合阶段(例如 $group ,或 $lookup)创建涵盖所有相关字段的复合索引。

// Single field
db.collection.createIndex({ field: 1 })

// Multiple fields (filter + sort)
db.collection.createIndex({ filterField: 1, sortField: 1 })

// Aggregation pipeline with $match on multiple fields
db.collection.createIndex({ status: 1, createdAt: -1 })

检查的文档与返回的文档的比率高

比较totalDocsExaminednReturned。 很大的差异意味着查询会扫描许多文档,但返回的次数很少。

指标:totalDocsExamined 明显高于 nReturned

"nReturned": 5,
"totalDocsExamined": 50000

行动: 添加更具选择性的索引或优化查询筛选器以减少扫描范围。

内存排序

FETCHCOLLSCAN 阶段之后的 SORT 阶段表示排序操作会在加载文档后于内存中执行。 内存中排序会消耗内存并大规模降低查询速度。

指标: 计划中会显示一个阶段 SORT

"winningPlan": {
  "stage": "SORT",
  "inputStage": {
    "stage": "COLLSCAN"
  }
}

行动: 创建包含排序字段的索引,并遵循复合索引的相等、排序、范围(ESR)规则。

db.collection.createIndex({ filterField: 1, sortField: 1 })

执行时间缓慢

executionTimeMillis 值表示查询速度缓慢。

指标:executionTimeMillis 返回的文档数高于预期。

"executionTimeMillis": 8500,
"nReturned": 20

操作:检查是否存在COLLSCAN、高totalDocsExamined或内存中SORT阶段,并使用适当的索引解决这些问题。

解读经过良好优化的查询语句

经过良好优化的查询会使用索引,仅扫描必要的键,并在无需进行内存排序的情况下返回文档。

"executionStats": {
  "nReturned": 10,
  "totalKeysExamined": 10,
  "totalDocsExamined": 10,
  "executionTimeMillis": 2,
  "winningPlan": {
    "stage": "FETCH",
    "inputStage": {
      "stage": "IXSCAN",
      "indexName": "status_1_createdAt_1"
    }
  }
}

在本示例中:

  • totalKeysExamined 等于 nReturned,这意味着该索引具有选择性。
  • totalDocsExamined 等于 nReturned,这意味着不会获取额外的文档。
  • 该执行计划先使用 IXSCAN,后接 FETCH,这符合索引查询的预期模式。

聚合管道的运行说明

在聚合管道上运行 explain() 以查看每个阶段的执行计划。

db.collection.explain("executionStats").aggregate([
  { $match: { status: "active" } },
  { $sort: { createdAt: -1 } },
  { $limit: 10 }
])

输出包括一个 stages 数组,其中显示了每个管道阶段如何映射到内部执行计划。 检查第一阶段中是否有 COLLSCANIXSCAN,以确认 $match 筛选器是否使用索引。