View a markdown version of this page

规划缓存过滤器 - Amazon DocumentDB

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

规划缓存过滤器

计划缓存过滤器(也称为索引筛选器)限制了允许查询计划器针对特定查询形状考虑的一组索引。当查询匹配具有过滤器的形状时,计划者仅从该过滤器中命名的索引中选择计划,而不是评估集合中的每个索引。

查询形状是查询谓词、排序规范和集合命名空间的组合,谓词值已标准化。两个仅值不同的查询共享一个形状,因此一个过滤器可以覆盖所有查询。在的输出中planCacheListFilters,标准化值显示为"@"。

计划缓存过滤器为您提供了一种固定计划的方法,这样您就可以避免由于数据分布或索引变化而选择较慢的索引。过滤器具有以下属性:

  • 无需更改应用程序。过滤器使用数据库命令设置并应用到服务器上,因此无需部署代码即可缓解回归。这是与 a 的主要区别hint,后者必须在每个呼叫站点添加。

  • 范围是一个查询形状。形状由集合、谓词和排序定义,而不是由完整查询定义,因此过滤器仅限制与该形状匹配的查询。对同一馆藏的其他查询仍在正常计划之中。

  • 这种变化是持久的。过滤器在实例重启和引擎修补后仍然有效。删除该集合会移除其过滤器。

  • 这种变化是可逆的。清除过滤器会使形状恢复到基于成本的正常计划,因此过滤器是一种在调查根本原因时稳定工作负载的低风险方法。

支持的 命令

计划缓存过滤器需要计划器版本 2.0 或更高版本。筛选条件可以应用的命令取决于计划器版本和 Amazon DocumentDB 次要版本,如下表所示。

对计划缓存过滤器的命令支持
命令 最低计划器版本 最低次要版本

find

2.0

5.0.0、8.0.0

count

2.0

5.0.0、8.0.0

update

2.0

5.0.2、8.0.2

delete

2.0

5.0.2、8.0.2

findAndModify

2.0

5.0.2、8.0.2

distinct

3.0

8.0.0

aggregate

3.0

8.0.2

注意

Planner 版本 3.0 仅在 Amazon DocumentDB 8.0 中可用。由于distinct和aggregate命令需要计划器版本 3.0,因此这两个命令的计划缓存筛选器仅在 Amazon DocumentDB 8.0 中可用。在支持计划器版本 2.0 的 Amazon DocumentDB 5.0 中find,筛选器适用于countupdate、delete、和findAndModify命令。

对于aggregate命令,形状取自管道的前端:前端$match提供过滤器,$sort紧随其后的是过滤器。$skip并且$limit不会影响形状,超过该点的阶段也不会影响形状。

Amazon DocumentDB 会在规划管道之前对其进行优化,并将计划缓存筛选器与优化的管道进行匹配。因此,计划者看到的筛选和排序可能与您编写的阶段有所不同。例如,管道前面的两个相邻$match阶段合并为一个$and谓词:

db.orders.aggregate([ { $match: { status: "SHIPPED" } }, { $match: { region: "us-east-1" } }, { $group: { _id: "$customerId", total: { $sum: "$amount" } } } ])

计划者看到一个$match开启{$and: [{status: ...}, {region: ...}]},因此必须在该组合谓词上设置过滤器。

db.runCommand({planCacheSetFilter: "orders", query: { $and: [ { status: "SHIPPED" }, { region: "us-east-1" } ] }, indexes: [ "status_1_region_1" ]})

在$lookup或$graphLookup阶段的外部集合上设置的过滤器驱动用于扫描该外国集合的索引。

支持的查询形状

从 Amazon DocumentDB 5.0.2 和 8.0.2 开始,以下运算符可以出现在具有计划缓存筛选器的查询形状中:

  • $text。搜索字符串是标准化的,因此仅在搜索词上不同的查询共享一个形状。

  • $near 和 $nearSphere 两个运算符均归一化为相同的形状。坐标$minDistance、和$maxDistance不是形状的一部分。

  • $geoWithin 和 $geoIntersects GeoJSON 几何类型是形状的一部分,因此Polygon查询和MultiPolygon查询是不同的形状。坐标不是形状的一部分。

  • $regex,直接在球场上使用时。模式和选项已标准化,因此同一字段上的所有正则表达式共享一个形状。

为使用$jsonSchema、$expr或$sampleRate的查询形状设置过滤器失败,错误代码为 303。对于以下形状,它也失败了。使用其中任何一种的查询都是在计划中不使用过滤器的:

  • $regex嵌套在里面$in$nin、或$all。将正则表达式与文字值混合在一起的数组无法根据索引进行评估,因此永远无法对该形状应用过滤器。

  • 的排序规范{$natural: 1}。$natural排序请求集合扫描,这与将计划器限制为索引不兼容。

设置、列出和清除筛选器

要设置计划缓存筛选器,请使用planCacheSetFilter命令。query和sort字段共同指定了查询形状,并indexes列出了允许规划者为该形状考虑的索引名称。

db.runCommand({ planCacheSetFilter: <collection>, query: <query>, sort: <sort>, // optional indexes: [ <index1>, <index2>, ...], comment: <any> // optional })

例如,以下命令将计划器限制为不进行排序的形状{a: {$eq: "@"}, b: {$eq: "@"}}的a_1索引:

db.runCommand({planCacheSetFilter: "foo", query: { a: 1, b: 1 }, sort: {}, indexes: [ "a_1" ]})

为已有滤镜的形状设置滤镜将替换现有滤镜。运行以下两个命令后,形状上的过滤器{a: {$eq: "@"}, b: {$eq: "@"}}为["b_1"]:

db.runCommand({planCacheSetFilter: "foo", query: { a: 1, b: 1 }, sort: {}, indexes: [ "a_1" ]}) db.runCommand({planCacheSetFilter: "foo", query: { a: 6, b: 10 }, sort: {}, indexes: [ "b_1" ]})

要列出集合中的所有过滤器,请使用以下planCacheListFilters命令:

db.runCommand({planCacheListFilters: <collection>})

输出显示每个存储的形状及其标准化值和允许的索引列表:

{ "filters" : [ { "query" : { "a" : { "$eq" : "@" } }, "sort" : { }, "indexes" : [ "a_1" ] }, { "query" : { "a" : { "$gt" : "@" } }, "sort" : { "a" : 1 }, "indexes" : [ "a_1_b_1" ] } ], "ok" : 1 }

运算符被保留,只有值被替换为"@"。隐式相等性{a: 1},例如以其显式形式列出{"a": {"$eq": "@"}},$in数组的元素作为一个整体替换,为{"a": {"$in": "@"}}。列出未进行排序的筛选器集时,sort文档为空。

要移除过滤器,请使用planCacheClearFilters命令。

db.runCommand({ planCacheClearFilters: <collection>, query: <query pattern>, // optional sort: <sort specification>, // optional comment: <any> // optional })

要清除集合中的所有过滤器,请省略这两个query和sort:

db.runCommand({planCacheClearFilters: "foo"})

同时提供这两个query选项,则仅sort清除排序完全相同的过滤器。中的值query被归一化掉,因此任何代表值都有效。要仅清除未进行排序的过滤器,请传递一个空sort文档:

db.runCommand({planCacheClearFilters: "foo", query: {a: 1}, sort: {a: 1}}) db.runCommand({planCacheClearFilters: "foo", query: {a: 1}, sort: {}})

验证是否应用了过滤器

explain输出包括两个报告计划缓存过滤器状态的字段:

  • indexFilterSet是true当集合上存在查询形状的过滤器时。

  • indexFilterAppliedtrue仅当使用该过滤器列表中的索引计划查询时。

从 Amazon DocumentDB 5.0.2 和 8.0.2 开始,每条支持计划缓存筛选器的命令都会报告这些字段,并在这些命令的分析器输出中报告这些字段。在早期的次要版本中,它们仅针对find命令进行了报告。有关阅读 explain 输出的更多信息,请参阅查询计划分析。

过滤器无需应用即可匹配查询形状。如果过滤器列表中的所有索引都无法为查询提供服务,则计划者在没有过滤器的情况下重新规划,并选择成本指数。在这种情况下,indexFilterSet是true、indexFilterApplied是false。当命名索引不覆盖查询的字段、命名索引不存在或$regex形状无法生成索引边界(例如未锚定或不区分大小写的模式)时,就会发生这种情况。

示例

以下示例使用orders集合和customers集合作为$lookup示例,索引如下:

db.orders.createIndex({ status: 1 }) // status_1 db.orders.createIndex({ status: 1, orderDate: 1 }) // status_1_orderDate_1 db.orders.createIndex({ customerId: 1 }) // customerId_1 db.customers.createIndex({ customerId: 1 }) // customerId_1

示例:将查找查询固定到复合索引

假设一个按以下条件进行过滤status和排序的查询orderDate:

db.orders.find({ status: "SHIPPED" }).sort({ orderDate: 1 })

复合索引同时status_1_orderDate_1满足谓词和排序,因此无需单独排序。如果计划者改为选择status_1,则查询必须对其结果进行排序,这会增加一个SORT阶段,并且随着匹配订单数量的增加而变得更加昂贵。要将计划器限制在此形状的复合索引范围内,请执行以下操作:

db.runCommand({planCacheSetFilter: "orders", query: { status: "SHIPPED" }, sort: { orderDate: 1 }, indexes: [ "status_1_orderDate_1" ]})

因为值是按形状归一化的,所以这个过滤器还覆盖{ status: "PENDING" }{ status: "CANCELLED" }、以及所有其他相同排序status的值。它不使用不同的排序或不包含排序的同一个谓词,它们是不同的形状。使用以下命令确认过滤器已生效explain:

db.orders.find({ status: "SHIPPED" }).sort({ orderDate: 1 }).explain()

输出报告indexFilterSet: true和indexFilterApplied: true,以及IXSCAN阶段名称status_1_orderDate_1。

示例:将更新固定到选择性索引

更新必须先找到要修改的文档,然后才能写入它们,并且该查询使用索引的方式与读取相同。当一个谓词可以由多个索引提供服务时,规划者选择的索引决定更新检查的文档数量。

db.orders.updateMany( { customerId: 4815, status: "PENDING" }, { $set: { status: "CANCELLED" } } )

两者customerId_1兼而有之status_1,都可以使用这个谓词。要将更新固定到customerId_1:

db.runCommand({planCacheSetFilter: "orders", query: { customerId: 4815, status: "PENDING" }, indexes: [ "customerId_1" ]})

写入命令的过滤器需要 Amazon DocumentDB 5.0.2 或 8.0.2 和 Planner 版本 2.0 或更高版本。使用以下更新explain进行验证:

db.runCommand({explain: {update: "orders", updates: [{ q: { customerId: 4815, status: "PENDING" }, u: { $set: { status: "CANCELLED" } }, multi: true }]}})

制胜计划是一个不停IXSCAN的UPDATE阶段customerId_1。同样的过滤器适用于delete使用此查询形状的findAndModify命令,因为该形状不包括写入类型。

示例:固定聚合管道和 $lookup 外国集合

对于聚合,形状取自前一$match阶段,$sort紧接其后。除此之外的阶段,例如$group或$project,不属于形状的一部分。

db.orders.aggregate([ { $match: { status: "SHIPPED" } }, { $group: { _id: "$customerId", total: { $sum: "$amount" } } } ])

在$match谓词上设置的过滤器驱动用于读orders取的索引:

db.runCommand({planCacheSetFilter: "orders", query: { status: "SHIPPED" }, indexes: [ "status_1" ]})

过滤器也适用于$lookup或$graphLookup阶段加入的集合。过滤器是在外部集合上设置的,而不是在管道运行的集合上设置的。在以下管道中,orders是外部集合,customers是外国集合:

db.orders.aggregate([ { $lookup: { from: "customers", localField: "customerId", foreignField: "customerId", as: "customer" } } ])

联接会对每个外部文档进行customers一次探测,因此在那里选择的索引会被重复应用,其成本在整个管道中成倍增加。要将该扫描固定到特定索引,请customers为联接密钥的相等形状设置过滤器:

db.runCommand({planCacheSetFilter: "customers", query: { customerId: 1 }, indexes: [ "customerId_1" ]})

因为值是按形状归一化的,所以1上面的值是一个占位符,任何值都会生成相同的过滤器。当应用国外集合筛选条件时,顶层indexFilterSet和indexFilterApplied字段报告true以及所选筛选条件的索引将显示在获胜计划的外部。开启过滤器aggregate需要亚马逊 DocumentDB 8.0.2 和计划器版本 3.0。

带有提示和索引更改的行为

  • 计划缓存过滤器的优先级高于hint。Amazon DocumentDB 首先应用过滤器,然后在过滤器的允许索引列表中应用提示。如果提示命名了过滤器允许的索引,则使用该索引。如果提示命名了过滤器不允许的索引,则该提示将被忽略,计划者根据成本在允许的索引中进行选择。无论是命名索引还是给出其键模式,这两种提示表单的行为方式都相同。

    例如,如果集合foo的索引为a_1b_1a_1_b_1、和,并且您使用索引列表在形状{query: {a: {$eq: "@"}, b: {$eq: "@"}}, sort: {a: 1}}上设置了过滤器["a_1", "a_1_b_1"],则运行会db.foo.find({ a: 10, b: 20 }).sort({a: 1}).hint({ a: 1 })使用a_1,因为该过滤器是在提示中命名的,并且是过滤器允许的。

  • 删除索引不会修改过滤器。过滤器保留索引名称。尽管缺少该索引,但规划却忽略了它。如果过滤器列表中的另一个索引可以为查询提供服务,则过滤器仍然适用。只有当命名索引都不可用时,才会indexFilterApplied报告false。重新创建同名索引使其再次符合条件。

  • 删除一个集合会移除该集合的所有过滤器。