在 MongoDB 查询优化器中选择完美索引的冲突
Conflict in choosing the perfect index in MongoDB query optimizer
我的问题与 MongoDB 的查询优化器有关,以及它如何选择要使用的完美索引。我意识到在某些情况下,优化器不会选择完美的现有索引,而是继续使用足够接近的索引。
考虑拥有一个简单的数据集,例如:
{ "_id" : 1, "item" : "f1", "type" : "food", "quantity" : 500 }
{ "_id" : 2, "item" : "f2", "type" : "food", "quantity" : 100 }
{ "_id" : 3, "item" : "p1", "type" : "paper", "quantity" : 200 }
{ "_id" : 4, "item" : "p2", "type" : "paper", "quantity" : 150 }
{ "_id" : 5, "item" : "f3", "type" : "food", "quantity" : 300 }
{ "_id" : 6, "item" : "t1", "type" : "toys", "quantity" : 500 }
{ "_id" : 7, "item" : "a1", "type" : "apparel", "quantity" : 250 }
{ "_id" : 8, "item" : "a2", "type" : "apparel", "quantity" : 400 }
{ "_id" : 9, "item" : "t2", "type" : "toys", "quantity" : 50 }
{ "_id" : 10, "item" : "f4", "type" : "food", "quantity" : 75 }
然后想发出如下查询:
db.inventory.find({"type": "food","quantity": {$gt: 50}})
我继续创建以下索引:
db.inventory.ensureIndex({"quantity" : 1, "type" : 1})
cursor.explain() 的统计数据证实该索引具有以下性能:("n":4,"nscannedObjects":4,"nscanned":9)。它扫描了比完全匹配数更多的索引。考虑到 "type" 是具有已识别匹配的更高选择性属性这一事实,创建以下索引肯定更好:
db.inventory.ensureIndex({ "type" : 1, "quantity" : 1})
统计数据也证实该指数表现更好:("n" : 4, "nscannedObjects" : 4, "nscanned" : 4)。这意味着第二个索引需要扫描与匹配文档完全相同数量的索引。
但是,我观察到如果我不删除第一个索引,查询优化器会继续使用第一个索引,尽管创建了更好的索引。
根据文档,每次创建新索引时,查询优化器都会考虑它来制定查询计划,但我在这里没有看到这种情况。
谁能解释查询优化器的真正工作原理?
Considering the fact that "type" is a higher selective attribute
索引选择性是一个非常重要的方面,但在这种情况下,请注意您使用的是类型相等查询和数量范围查询,这是交换索引顺序的更有说服力的理由,即使选择性较低。
However, I observed if I don't delete the first index, the query optimizer continues using the first index, although the better index is got created. [...]
MongoDB 查询优化器主要是统计。与大多数 SQL 引擎不同,MongoDB 不会尝试推断什么可能是效率更高或更低的索引。相反,它只是不时地并行运行不同的查询,并记住哪个查询更快。然后将使用更快的策略。有时,MongoDB 会再次执行并行查询并重新评估策略。
这种方法的一个问题(也可能是造成混淆的原因)是与这么小的数据集可能没有太大区别——简单地扫描元素通常比使用 any 一种索引或搜索策略,如果数据与预取/页面大小/缓存大小和管道长度相比不大。根据经验,最多可能包含 100 个甚至 1,000 个元素的简单列表通常根本无法从索引中获益。
就像做任何更伟大的事情一样,设计索引需要一些前瞻性的思考。目标是:
- 效率 - 快速读/写操作
- 选择性 - 最小化记录扫描
- 其他要求 - 例如如何处理排序?
选择性是决定索引使用效率的主要因素。理想情况下,索引使我们能够 select 仅完成结果集所需的那些记录,而无需扫描大量索引键(或文档)以完成查询。选择性决定了任何后续操作必须处理的记录数。更少的记录意味着更少的执行时间。
考虑应用程序最常使用的查询。使用 explain
命令,具体见 executionStats
:
nReturned
totalKeysExamined
- 如果检查的键数比返回的文件大很多?我们需要一些索引来减少它。
看看queryPlanner
、rejectedPlans
。查看 winningPlan
显示 keyPattern
显示哪些键需要索引。每当我们看到 stage:SORT
时,就意味着要排序的键不是索引的一部分,或者数据库无法根据数据库中指定的排序顺序对文档进行排序。并且需要执行内存排序。如果我们添加排序所基于的键,我们将看到 winningPlan
's' stage
从 SORT
变为 FETCH
。索引中的键需要根据它们的数据范围来指定。例如:class 的音量将小于 student。这样做需要我们有所取舍。虽然 executionTimeMillis
会非常少,但 docsExamined
和 keysExamined
会相对 小 大。但这种权衡是值得的。
还有一种方法可以强制查询使用特定索引,但不建议将其作为部署的一部分。所关注的命令是 .hint()
可以链接在 find
或 sort
之后进行排序等。它需要实际的索引名称或索引的形状。
一般来说,在为以下对象构建复合索引时:
- equality 字段:查询将执行相等性测试的字段
- 排序字段:查询将指定排序的字段
- 范围字段:查询执行范围测试的字段
我们应该牢记以下经验法则:
- 范围字段之前的相等字段
- 在范围字段之前对字段排序
- 排序字段前的相等字段
我的问题与 MongoDB 的查询优化器有关,以及它如何选择要使用的完美索引。我意识到在某些情况下,优化器不会选择完美的现有索引,而是继续使用足够接近的索引。
考虑拥有一个简单的数据集,例如:
{ "_id" : 1, "item" : "f1", "type" : "food", "quantity" : 500 }
{ "_id" : 2, "item" : "f2", "type" : "food", "quantity" : 100 }
{ "_id" : 3, "item" : "p1", "type" : "paper", "quantity" : 200 }
{ "_id" : 4, "item" : "p2", "type" : "paper", "quantity" : 150 }
{ "_id" : 5, "item" : "f3", "type" : "food", "quantity" : 300 }
{ "_id" : 6, "item" : "t1", "type" : "toys", "quantity" : 500 }
{ "_id" : 7, "item" : "a1", "type" : "apparel", "quantity" : 250 }
{ "_id" : 8, "item" : "a2", "type" : "apparel", "quantity" : 400 }
{ "_id" : 9, "item" : "t2", "type" : "toys", "quantity" : 50 }
{ "_id" : 10, "item" : "f4", "type" : "food", "quantity" : 75 }
然后想发出如下查询:
db.inventory.find({"type": "food","quantity": {$gt: 50}})
我继续创建以下索引:
db.inventory.ensureIndex({"quantity" : 1, "type" : 1})
cursor.explain() 的统计数据证实该索引具有以下性能:("n":4,"nscannedObjects":4,"nscanned":9)。它扫描了比完全匹配数更多的索引。考虑到 "type" 是具有已识别匹配的更高选择性属性这一事实,创建以下索引肯定更好:
db.inventory.ensureIndex({ "type" : 1, "quantity" : 1})
统计数据也证实该指数表现更好:("n" : 4, "nscannedObjects" : 4, "nscanned" : 4)。这意味着第二个索引需要扫描与匹配文档完全相同数量的索引。
但是,我观察到如果我不删除第一个索引,查询优化器会继续使用第一个索引,尽管创建了更好的索引。
根据文档,每次创建新索引时,查询优化器都会考虑它来制定查询计划,但我在这里没有看到这种情况。
谁能解释查询优化器的真正工作原理?
Considering the fact that "type" is a higher selective attribute
索引选择性是一个非常重要的方面,但在这种情况下,请注意您使用的是类型相等查询和数量范围查询,这是交换索引顺序的更有说服力的理由,即使选择性较低。
However, I observed if I don't delete the first index, the query optimizer continues using the first index, although the better index is got created. [...]
MongoDB 查询优化器主要是统计。与大多数 SQL 引擎不同,MongoDB 不会尝试推断什么可能是效率更高或更低的索引。相反,它只是不时地并行运行不同的查询,并记住哪个查询更快。然后将使用更快的策略。有时,MongoDB 会再次执行并行查询并重新评估策略。
这种方法的一个问题(也可能是造成混淆的原因)是与这么小的数据集可能没有太大区别——简单地扫描元素通常比使用 any 一种索引或搜索策略,如果数据与预取/页面大小/缓存大小和管道长度相比不大。根据经验,最多可能包含 100 个甚至 1,000 个元素的简单列表通常根本无法从索引中获益。
就像做任何更伟大的事情一样,设计索引需要一些前瞻性的思考。目标是:
- 效率 - 快速读/写操作
- 选择性 - 最小化记录扫描
- 其他要求 - 例如如何处理排序?
选择性是决定索引使用效率的主要因素。理想情况下,索引使我们能够 select 仅完成结果集所需的那些记录,而无需扫描大量索引键(或文档)以完成查询。选择性决定了任何后续操作必须处理的记录数。更少的记录意味着更少的执行时间。
考虑应用程序最常使用的查询。使用 explain
命令,具体见 executionStats
:
nReturned
totalKeysExamined
- 如果检查的键数比返回的文件大很多?我们需要一些索引来减少它。
看看queryPlanner
、rejectedPlans
。查看 winningPlan
显示 keyPattern
显示哪些键需要索引。每当我们看到 stage:SORT
时,就意味着要排序的键不是索引的一部分,或者数据库无法根据数据库中指定的排序顺序对文档进行排序。并且需要执行内存排序。如果我们添加排序所基于的键,我们将看到 winningPlan
's' stage
从 SORT
变为 FETCH
。索引中的键需要根据它们的数据范围来指定。例如:class 的音量将小于 student。这样做需要我们有所取舍。虽然 executionTimeMillis
会非常少,但 docsExamined
和 keysExamined
会相对 小 大。但这种权衡是值得的。
还有一种方法可以强制查询使用特定索引,但不建议将其作为部署的一部分。所关注的命令是 .hint()
可以链接在 find
或 sort
之后进行排序等。它需要实际的索引名称或索引的形状。
一般来说,在为以下对象构建复合索引时: - equality 字段:查询将执行相等性测试的字段 - 排序字段:查询将指定排序的字段 - 范围字段:查询执行范围测试的字段
我们应该牢记以下经验法则:
- 范围字段之前的相等字段
- 在范围字段之前对字段排序
- 排序字段前的相等字段