本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
Index-only 扫描(涵盖的查询)
纯索引扫描(也称为覆盖查询)是一种优化,其中 Amazon DocumentDB 完全根据存储在索引中的字段回答查询,而无需读取基础文档。由于索引中已经存在查询所需的值,因此 Amazon DocumentDB 可以跳过获取每个匹配的文档,从而避免读取完整文档的随机成本 I/O 和解码成本。在查询计划中,仅索引扫描显示为IXONLYSCAN阶段,而不是IXSCAN(索引扫描)或COLLSCAN(收集扫描)。这可以减少 I/O 并显著降低所支持查询的查询延迟。
当查询仅涉及全部存在于单个索引中的少量字段时,尤其是在文档本身很大的情况下,它是纯索引扫描的理想选择。常见的示例包括仪表板、分析汇总和从更广泛的复合索引中读取几个字段的查询。
无论是否使用仅索引扫描,查询的结果都是相同的。只有查询计划及其执行量 I/O 会发生变化。
注意
Index-only 扫描支持取决于您的 Amazon DocumentDB 引擎版本:
-
包含多个非
_id字段的find投影需要引擎版本 8.0.2 或更高版本。在早期版本中,覆盖find投影最多可以包含一个非_id字段。具有两个或更多字段的投影会退回到正常的索引扫描或集合扫描。 -
Index-only 扫描聚合管道(例如 covered)需要引擎版本 8.0.2 或更高版本。
$group
当 Amazon DocumentDB 使用仅限索引的扫描时
当单个索引包含操作读取的每个字段时,Amazon DocumentDB 会考虑仅对以下操作进行索引扫描。
涵盖的查找预测
如果find查询的预测仅包含一个或多个顶级字段,并且每个投影字段都存在于单个索引中,则该查询符合条件。例如,如果复合索引为 on{ customerId: 1, status: 1, total: 1 },则将涵盖以下查询。所有三个投影字段都存在于索引中,并且_id被明确排除在外。(覆盖包含多个非_id字段的投影需要引擎版本 8.0.2 或更高版本。)
db.orders.createIndex({ customerId: 1, status: 1, total: 1 }) db.orders.find( { customerId: 42 }, { _id: 0, customerId: 1, status: 1, total: 1 } )
获胜计划使用IXONLYSCAN阶段,从不从存储空间中提取匹配的文档:
{ "stage": "IXONLYSCAN", "indexName": "customerId_1_status_1_total_1", "direction": "forward", "indexCond": { "$and": [ { "customerId": { "$eq": 42 } } ] } }
注意
在涵盖的find查询中,结果中的字段按索引顺序(字段在索引定义中出现的顺序)返回,而不是按照它们在投影中列出的顺序或它们在文档中的存储顺序返回。这与涵盖查询的 MongoDB 行为相匹配。相反,由集合扫描回答的查询会按存储文档的顺序返回字段。
涵盖的 $group 聚合
一个$group阶段通常只读取几个字段(组键及其累加器的输入),但通常在没有前导$match的情况下运行。如果没有覆盖索引,Amazon DocumentDB 会读取和解码集合中的每个完整文档,只是为了将这几个字段输入到分组中。
Index-only 引擎版本 8.0.2 及更高版本中提供了对聚合管道的扫描,包括覆盖的$group阶段。
当单个索引包含管道前缀读取的每个字段时,Amazon DocumentDB 可以在该组下方进行仅限索引的扫描,并直接从索引中读取这些字段。假设一个按状态对订单进行分组并求和其总数的管道,其复合索引为:{ status: 1, total: 1 }
db.orders.createIndex({ status: 1, total: 1 }) db.orders.aggregate([ { $group: { _id: "$status", revenue: { $sum: "$total" } } } ])
管道只读取status和total,两者都在索引中,因此该组是由集合扫描IXONLYSCAN而非集合扫描提供的:
// Without a covering index $group <- COLLSCAN (reads and decodes every full document) // With a covering index on { status, total } $group <- IXONLYSCAN over { status, total } (reads only the referenced fields)
支持前导$match$sort$skip、、或$limit之前。$group但是,这些阶段引用的每个字段(例如,$match过滤器字段或$sort密钥)也必须存在于同一个索引中才能覆盖管道。如果索引中缺少任何引用字段,Amazon DocumentDB 将回退到收集或索引扫描。
注意
与find投影不同,a $group 不隐含包含文档_id。a _id 中的$group是组密钥,而不是文档标识符,因此索引不需要包含文档_id来覆盖管道。
涵盖查询的要求
要让 Amazon DocumentDB 使用仅限索引的扫描,必须满足以下所有条件:
-
单个索引包含查询读取的每个字段。这包括所覆盖
$group管道的find投影字段、群组密钥、累加器输入以及任何过滤器或排序字段。如果索引中甚至缺少一个字段,则无法覆盖查询。 -
该投影包含顶级字段(用于
find)。排除预测、计算字段和投影运算符(例如$slice和)$elemMatch不符合条件。 -
该
_id字段已被考虑在内(用于find)。因为_id默认情况下包含在内,所以常见的复合索引(例如){ a: 1, b: 1 }不包括find({}, { a: 1, b: 1 }):隐式索引_id不在索引中。要么_id从投影中排除 ({ _id: 0, a: 1, b: 1 }),要么_id添加到索引中。 -
该指数完全满足过滤器的要求。如果 Amazon DocumentDB 无法将查询谓词的一部分转换为索引绑定,它将保留该部分作为剩余过滤器,读取文档以对其进行评估,并且不会将扫描转换为仅限索引的扫描。
$mod、$ne$nin、和等运算符会$exists留下残差过滤器。 -
任何投影字段都不是多键(数组)列。请参阅限制。
限制
以下情况不能使用纯索引扫描,只能使用普通索引扫描或集合扫描。这是预期的行为,不会影响结果的正确性。
-
多键(数组)字段。如果一个字段曾经在任何文档中保存过数组值,则其索引列为多键。多键索引为每个数组元素存储一个条目,因此无法从索引中重建原始数组。任何读取此类字段的查询都不包括在内。
-
虚线路径或嵌套路径。嵌套路径(如)上的投影或字段引用
"address.city"不符合条件,即使索引包含该路径也是如此。 -
排除、计算或运算符预测。诸如
{ b: 0 }{ total: { $add: ["$price", "$tax"] } }、或之类的预测{ a: { $slice: 5 } }需要完整文档,不包括在内。 -
Whole-document 聚合中的引用。
$group引用整个文档(例如{ $push: "$$ROOT" })的 A 无法被覆盖,因为没有索引可以重建整个文档。 -
重塑之前
$group的阶段。重塑之前出现的文档(例如$project、$addFields$unwind、或$lookup)的阶段会使管道$group失去被覆盖的资格。 -
字符串谓词的排序规则不匹配。如果查询的排序规则与索引的排序规则不同,则索引无法提供字符串谓词并留下残留过滤器,这会强制提取文档。数字和基于类型的谓词不受影响。
确认仅限索引的扫描
使用explain("executionStats")输出来确认查询是由仅限索引的扫描提供的,并且它没有触及文档。寻找两件事:
-
获胜计划使用
IXONLYSCAN舞台。 -
该阶段的
docsExamined值是0(并且totalDocsExamined是0),这意味着查询完全是从索引中回答的,没有读取任何文档。
db.orders.find( { customerId: 42 }, { _id: 0, customerId: 1, status: 1, total: 1 } ).explain("executionStats").executionStats
{ "executionSuccess": true, "nReturned": "2", "executionTimeMillis": "0.714", "totalDocsExamined": "0", "executionStages": { "stage": "IXONLYSCAN", "nReturned": "2", "indexName": "customerId_1_status_1_total_1", "direction": "forward", "docsExamined": "0" } }
注意
IXONLYSCAN舞台上的非零docsExamined值表示索引包含数据,但是 Amazon DocumentDB 仍在检查存储空间以确认每个文档版本都可见。当集合中有最近写入的过时文档版本垃圾回收尚未回收时,就会发生这种情况。垃圾回收处理完这些文档后,将其docsExamined丢弃到0。此时,该查询实现了覆盖扫描的全部好处。有关垃圾收集的更多信息,请参阅Amazon DocumentDB 中的垃圾回收。有关 docsExamined 指标的更多信息,请参阅查询计划分析。
最佳实践
-
设计包含最常查询项目或分组的字段的复合索引,这样就可以涵盖这些查询。
-
排除在
find预测_id之外 ({ _id: 0, ... })_id,除非是覆盖指数的一部分。 -
当您打算涵盖查询时,请避免为数组(多键)字段建立索引,因为多键列会取消仅限索引路径的资格。
-
允许垃圾回收以适应您的写入工作量,从而使覆盖的扫描跳过文档访问并
docsExamined保持不变0。有关更多信息,请参阅 Amazon DocumentDB 中的垃圾回收。