MongoDB 4.2高效索引策略:优化索引结构、选择合适的索引类型、权衡查询性能和存储空间
  nQkVcpdWfLDr 2023年11月02日 31 0

应用程序的最佳索引必须考虑许多因素,包括您期望的查询类型,读取与写入的比率以及系统上的可用内存量。

在开发索引策略时,您应该深入了解应用程序的查询。 在构建索引之前,映射将要运行的查询类型,以便您可以构建引用这些字段的索引。 索引具有性能成本,但是对于大型数据集上的频繁查询而言,它们的价值更高。 考虑应用程序中每个查询的相对频率以及查询是否证明索引是合理的。

设计索引的最佳总体策略是使用与您将在生产中运行的数据集类似的数据集来分析各种索引配置,以查看哪些配置性能最佳。 检查为集合创建的当前索引,以确保它们支持您当前和计划的查询。 如果不再使用索引,请删除索引。

通常,MongoDB仅使用一个索引来完成大多数查询。 但是, $or查询的每个子句可能使用不同的索引,从2.6开始,MongoDB可以使用多个索引的交集 。

以下文档介绍了索引策略:

1.创建索引以支持您的查询

当索引包含查询扫描的所有字段时,索引支持查询。 查询扫描索引而不是集合。 创建支持查询的索引可以大大提高查询性能。  

本文档介绍了创建支持查询的索引的策略。

(1).如果所有查询使用相同的单个键,则创建单键索引

如果您只查询给定集合中的单个键,则需要为该集合创建一个单键索引。 例如,您可以在product集合中的category上创建索引:

db.products.createIndex( { "category": 1 } )
(2).创建复合索引以支持几种不同的查询

如果您有时只查询一个key,而在其他时间查询该密钥并结合第二个key,则创建复合索引比创建单键索引更有效。 MongoDB将对两个查询使用复合索引。

例如,您可以在category和item上创建索引

db.products.createIndex( { "category": 1, "item": 1 } )

这允许您同时选择。 您可以只查询category ,也可以查询与item组合的category 。 多个字段上的单个复合索引可以支持搜索这些字段的“前缀”子集的所有查询。

例,集合上的以下索引:

{ x: 1, y: 1, z: 1 }

可以支持以下索引支持的查询:

{ x: 1 }
{ x: 1, y: 1 }

在某些情况下,前缀索引可以提供更好的查询性能:例如,如果z是一个大型数组。

{ x: 1, y: 1, z: 1 }索引也可以支持许多与以下索引相同的查询:

{ x: 1, z: 1 }

此外, { x: 1, z: 1 }还有一个用途。 给出以下查询:

db.collection.find( { x: 5 } ).sort( { z: 1} )

{ x: 1, z: 1 }索引支持查询和排序操作,而{ x: 1, y: 1, z: 1 }索引仅支持查询。 有关排序的详细信息,请参阅使用索引对查询结果进行排序 。

从2.6版开始,MongoDB可以使用索引交集来完成查询。 创建支持查询或依赖索引交集的复合索引之间的选择取决于系统的细节。 有关详细信息,请参见索引交点和复合索引 。

(3).索引使用和整理

要使用索引进行字符串比较,操作还必须指定相同的排序规则。 也就是说,如果操作指定了不同的排序规则,则具有排序规则的索引不能支持对索引字段执行字符串比较的操作。

例如,集合myColl在字符串字段category上具有索引,其中排序规则区域设置为"fr" 。

db.myColl.createIndex( { category: 1 }, { collation: { locale: "fr" } } )

以下查询操作(指定与索引相同的排序规则)可以使用索引:

db.myColl.find( { category: "cafe" } ).collation( { locale: "fr" } )

但是,以下查询操作(默认情况下使用“简单”二进制排序)无法使用索引:

db.myColl.find( { category: "cafe" } )

对于索引前缀键不是字符串,数组和嵌入文档的复合索引,指定不同排序规则的操作仍然可以使用索引来支持对索引前缀键的比较。

例如,集合myColl在数字字段score和price以及字符串字段category上有一个复合索引; 使用排序规则区域设置"fr"创建索引以进行字符串比较:

db.myColl.createIndex(
   { score: 1, price: 1, category: 1 },
   { collation: { locale: "fr" } } )

以下操作使用"simple"二进制排序规则进行字符串比较,可以使用索引:

db.myColl.find( { score: 5 } ).sort( { price: 1 } )
db.myColl.find( { score: 5, price: { $gt: NumberDecimal( "10" ) } } ).sort( { price: 1 } )

以下操作使用"simple"二进制排序规则对索引category字段进行字符串比较,可以使用索引仅满足查询的score: 5部分:

db.myColl.find( { score: 5, category: "cafe" } )

2.使用索引对查询结果进行排序

在MongoDB中,排序操作可以通过基于索引中的排序检索文档来获取排序顺序。 如果查询计划程序无法从索引获取排序顺序,它将在内存中对结果进行排序。 使用索引的排序操作通常比不使用索引的排序操作具有更好的性能。 此外, 不使用索引的排序操作将在使用32兆字节的内存时中止。

注意:由于对MongoDB 3.6中数组字段的排序行为进行了更改,因此在对使用多键索引编制索引的数组进行排序时,查询计划将包含阻塞SORT阶段。 新的排序行为可能会对性能产生负面影响。在阻塞SORT中,排序步骤必须使用所有输入才能生成输出。 在非阻塞或索引排序中,排序步骤扫描索引以按请求的顺序生成结果。

(1).用单字段索引排序

如果升序索引或降序索引位于单个字段上,则字段上的排序操作可以在任一方向上。

例如,在字段a为集合records创建升序索引:

db.records.createIndex( { a: 1 } )

此索引可以支持以下升序排序:

db.records.find().sort( { a: 1 } )

索引还可以通过以相反的顺序遍历索引来支持以下降序排序:

db.records.find().sort( { a: -1 } )
(2).按多个字段排序 

创建复合索引以支持在多个字段上进行排序。

您可以在索引的所有键或子集上指定排序; 但是,排序键必须按它们在索引中显示的顺序列出。 例如,索引键模式{ a: 1, b: 1 }可以支持{ a: 1, b: 1 }上的排序{ a: 1, b: 1 }但不能支持 { b: 1, a: 1 }上的排序。

对于使用复合索引进行排序的查询, cursor.sort()文档中所有键的指定排序方向必须与索引键模式匹配, 或者与索引键模式的反转匹配。 例如,索引键模式{ a: 1, b: -1 }可以支持对{ a: 1, b: -1 }和{ a: -1, b: 1 }的排序,但不支持 { a: -1, b: -1 }的排序{ a: -1, b: -1 }或{a: 1, b: 1} 。          

a.排序和索引前缀

如果排序键对应于索引键或索引前缀 ,MongoDB可以使用索引对查询结果进行排序。 复合索引的前缀是由索引键模式开头的一个或多个键组成的子集。

例如,在data集合上创建复合索引:

db.data.createIndex( { a:1, b: 1, c: 1, d: 1 } )

然后,以下是该索引的前缀:

{ a: 1 }
{ a: 1, b: 1 }
{ a: 1, b: 1, c: 1 }

以下查询和排序操作使用索引前缀对结果进行排序。 这些操作不需要在内存中对结果集进行排序。

MongoDB 4.2高效索引策略:优化索引结构、选择合适的索引类型、权衡查询性能和存储空间_排序规则

请考虑以下示例,其中索引的前缀键同时出现在查询谓词和排序中:

db.data.find( { a: { $gt: 4 } } ).sort( { a: 1, b: 1 } )

在这种情况下,MongoDB可以使用索引按排序指定的顺序检索文档。 如示例所示,查询谓词中的索引前缀可以与排序中的前缀不同。

b.索引的排序和非前缀子集

索引可以支持对索引键模式的非前缀子集的排序操作。 为此,查询必须在排序键之前的所有前缀键上包含相等条件。

例如,集合data具有以下索引:

{ a: 1, b: 1, c: 1, d: 1 }

以下操作可以使用索引来获取排序顺序:

MongoDB 4.2高效索引策略:优化索引结构、选择合适的索引类型、权衡查询性能和存储空间_排序规则_02

如上一个操作所示,只有排序子集之前的索引字段必须在查询文档中具有相等条件; 其他索引字段可以指定其他条件。

如果查询未在排序规范之前或与排序规范重叠的索引前缀上指定相等条件,则操作将无法有效地使用索引。 例如,以下操作指定{ c: 1 }的排序文档,但查询文档在前面的索引字段a和b上不包含相等匹配:

db.data.find( { a: { $gt: 2 } } ).sort( { c: 1 } )
db.data.find( { c: 5 } ).sort( { c: 1 } )

这些操作不会有效地使用索引{ a: 1, b: 1, c: 1, d: 1 } ,甚至可能不使用索引来检索文档。

(3).索引使用和排序

要使用索引进行字符串比较,操作还必须指定相同的排序规则。 也就是说,如果操作指定了不同的排序规则,则具有排序规则的索引不能支持对索引字段执行字符串比较的操作。

例如,集合myColl在字符串字段category上具有索引,其中排序规则区域设置为"fr" 。

db.myColl.createIndex( { category: 1 }, { collation: { locale: "fr" } } )

以下查询操作(指定与索引相同的排序规则)可以使用索引:

db.myColl.find( { category: "cafe" } ).collation( { locale: "fr" } )

但是,以下查询操作(默认情况下使用“简单”二进制文件夹)无法使用索引:

db.myColl.find( { category: "cafe" } )

对于索引前缀键不是字符串,数组和嵌入文档的复合索引,指定不同排序规则的操作仍然可以使用索引来支持对索引前缀键的比较。

例如,集合myColl在数字字段score和price以及字符串字段category上有一个复合索引; 使用排序规则区域设置"fr"创建索引以进行字符串比较:

db.myColl.createIndex(
   { score: 1, price: 1, category: 1 },
   { collation: { locale: "fr" } } )

以下操作使用"simple"二进制排序规则进行字符串比较,可以使用索引:

db.myColl.find( { score: 5 } ).sort( { price: 1 } )
db.myColl.find( { score: 5, price: { $gt: NumberDecimal( "10" ) } } ).sort( { price: 1 } )

以下操作使用"simple"二进制排序规则对索引category字段进行字符串比较,可以使用索引仅满足查询的score: 5部分:

db.myColl.find( { score: 5, category: "cafe" } )

3.保索引大小小于缓存大小

为了实现最快的处理,请确保索引完全适合RAM,以便系统可以避免从磁盘读取索引。

要检查索引的大小,请使用db.collection.totalIndexSize()帮助程序,它以字节为单位返回数据:

> db.collection.totalIndexSize()
4294976499

上面的示例显示了大约4.3千兆字节的索引大小。 为确保此索引适合RAM,您不仅必须拥有可用的RAM,还必须具有可用于其余工作集的 RAM。 还要记住:

如果您拥有并使用多个集合,则必须考虑所有集合上所有索引的大小。 索引和工作集必须能够同时适合内存。

在某些有限的情况下,索引不需要适合内存。 请参见仅在RAM中保留最近值的索引 。

也可以看看: 

collStats和db.collection.stats()

仅保留RAM中最近值的索引

在所有情况下,索引都不必完全适合RAM。 如果索引字段的值随每次插入而递增,并且大多数查询选择最近添加的文档; 那么MongoDB只需要保留索引中包含RAM中最新或“最右”值的部分。 这允许读取和写入操作的高效索引使用,并最小化支持索引所需的RAM量。  

4.创建确保选择性的查询

选择性是查询使用索引缩小结果的能力。 有效索引更具选择性,允许MongoDB将索引用于与完成查询相关的大部分工作。

要确保选择性,请编写使用索引字段限制可能文档数的查询。 编写相对于索引数据具有适当选择性的查询。例:

假设您有一个名为status的字段,其中可能的值是新的并且已处理 。 如果在status上添加索引,则表明您已创建低选择性索引。 该索引对于查找记录几乎没有帮助。

根据您的查询,更好的策略是创建包含低选择性字段和另一个字段的复合索引 。 例如,您可以在status和created_at.上创建复合索引created_at.

另一个选项,同样取决于您的用例,可能是使用单独的集合,每个状态一个。

例:考虑一个集合中的索引{ a : 1 } (即按键升序排序的索引),其中a有三个值均匀分布在集合中: 

{ _id: ObjectId(), a: 1, b: "ab" }
{ _id: ObjectId(), a: 1, b: "cd" }
{ _id: ObjectId(), a: 1, b: "ef" }
{ _id: ObjectId(), a: 2, b: "jk" }
{ _id: ObjectId(), a: 2, b: "lm" }
{ _id: ObjectId(), a: 2, b: "no" }
{ _id: ObjectId(), a: 3, b: "pq" }
{ _id: ObjectId(), a: 3, b: "rs" }
{ _id: ObjectId(), a: 3, b: "tv" }

如果查询{ a: 2, b: "no" } MongoDB必须扫描集合中的3个文档以返回一个匹配的结果。 同样,对{ a: { $gt: 1}, b: "tv" }必须扫描6个文档,同时返回一个结果。

在集合中考虑相同的索引,其中有九个值均匀分布在集合中:

{ _id: ObjectId(), a: 1, b: "ab" }
{ _id: ObjectId(), a: 2, b: "cd" }
{ _id: ObjectId(), a: 3, b: "ef" }
{ _id: ObjectId(), a: 4, b: "jk" }
{ _id: ObjectId(), a: 5, b: "lm" }
{ _id: ObjectId(), a: 6, b: "no" }
{ _id: ObjectId(), a: 7, b: "pq" }
{ _id: ObjectId(), a: 8, b: "rs" }
{ _id: ObjectId(), a: 9, b: "tv" }

如果查询{ a: 2, b: "cd" } ,MongoDB必须只扫描一个文档才能完成查询。 索引和查询更具选择性,因为a的值均匀分布,查询可以使用索引选择特定文档。

但是,虽然a上的索引更具选择性,但{ a: { $gt: 5 }, b: "tv" }仍需要扫描4个文档。

如果总体选择性较低,并且如果MongoDB必须读取大量文档以返回结果,那么一些查询可能在没有索引的情况下执行得更快。 要确定性能,请参阅Measure Index Use.

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  lG7RE7vNF4mc   2023年11月13日   30   0   0 3d权重字段
  wpWn7yzs0oKF   2023年11月13日   37   0   0 数据库字段SQL
  dIZ4mPo2q5Ch   2023年11月02日   64   0   0 字段分隔符CentOS
  nQkVcpdWfLDr   2023年11月13日   34   0   0 数据2d字段
nQkVcpdWfLDr