索引通过减少查询操作需要处理的数据量来提高读取操作的效率。 这简化了与在MongoDB中完成查询相关的工作。

1.创建索引以支持读取操作

如果应用程序在特定字段或字段集上查询集合,则查询字段上的索引或字段集上的复合索引可能会阻止查询扫描整个集合以查找并返回查询结果。 有关索引的更多信息,请参阅MongoDB中索引的完整文档 。例:应用程序在type字段上查询inventory集合。 type字段的值是用户驱动的。

var typeValue = <someUserInput>;
db.inventory.find( { type: typeValue } );

要提高此查询的性能,请在type字段上向inventory集合添加升序或降序索引。 在mongo shell中,您可以使用db.collection.createIndex()方法创建索引:

db.inventory.createIndex( { type: 1 } )

此索引可以防止上面的type查询扫描整个集合以返回结果。

要使用索引分析查询的性能 ,请参阅分析查询性能 。

除了优化读取操作之外,索引还可以支持排序操作并允许更高效的存储利用率。 有关索引创建的更多信息,请参见db.collection.createIndex()和Indexes 。

2.查询选择性

查询选择性是指查询谓词排除或过滤集合中文档的程度。 查询选择性可以确定查询是否可以有效地使用索引,甚至可以只使用索引。

更具选择性的查询匹配较小百分比的文档。 例如,唯一_id字段上的相等匹配具有高度选择性,因为它最多可匹配一个文档。

选择性较低的查询匹配较大比例的文档。 选择性较低的查询无法有效地使用索引,甚至根本不能使

例如,不等式运算符nin和ne 不是很有选择性,因为它们经常匹配索引的大部分。 因此,在许多情况下,带索引的nin或ne查询可能不会比必须扫描集合中所有文档的nin或ne查询更好。

正则表达式的选择性取决于表达式本身。 有关详细信息,请参阅regular expression and index use.  

正则表达式索引的使用

对于区分大小写的正则表达式查询,如果该字段存在索引,则MongoDB将正则表达式与索引中的值匹配,这可能比集合扫描更快。 如果正则表达式是“前缀表达式”,则可以进一步优化,这意味着所有可能的匹配都以相同的字符串开头。 这允许MongoDB从该前缀构造“范围”,并且仅匹配来自该范围内的索引的那些值。

如果正则表达式以插入符号( ^ )或左键( \A )开头,后跟一串简单符号,则它是“前缀表达式”。 例如,regex /^abc.*/将通过仅匹配以abc开头的索引中的值进行优化。

另外,当/^a/ /^a./ /a/和/a./匹配等效字符串时,它们具有不同的性能特征。如果存在适当的索引,则所有这些表达式都使用索引;但是,//a.∗/。∗/和/a.∗/比较慢。 /^a/可以在匹配前缀后停止扫描。

不区分大小写的正则表达式查询通常无法有效地使用索引。 $regex实现不支持排序规则,并且无法使用不区分大小写的索引。

示例:以下示例使用包含以下文档的集合products

执行LIKE匹配,以下示例匹配sku字段类似于"%789"所有文档:

db.products.find( { sku: { $regex: /789$/ } } )

该示例类似于以下SQL LIKE语句:

SELECT * FROM products
WHERE sku like "%789";

执行不区分大小写的正则表达式匹配,以下示例使用该i选项对sku值为开头的文档执行不区分大小写的匹配ABC。

db.products.find( { sku: { $regex: /^ABC/i } } )

该查询与以下文档匹配:

{ "_id" : 100, "sku" : "abc123", "description" : "Single line description." }
{ "_id" : 101, "sku" : "abc789", "description" : "First line\nSecond line" }

3.覆盖查询

覆盖查询是可以使用索引完全满足的查询,而不必检查任何文档。 当以下两个条件适用时,索引将涵盖查询:

a.查询中的所有字段都是索引的一部分
b.结果中返回的所有字段都在同一索引中

例如,集合inventory在type和item字段上具有以下索引:

db.inventory.createIndex( { type: 1, item: 1 } )

此索引将涵盖以下操作,该操作查询type和item字段并仅返回item字段:

db.inventory.find(
   { type: "food", item:/^c/ },
   { item: 1, _id: 0 }
)

对于覆盖查询的指定索引,投影文档必须显式指定_id: 0以从结果中排除_id字段,因为索引不包含_id字段。

版本3.6中已更改:索引可以涵盖嵌入文档中字段的查询(要索引嵌入文档中的字段,请使用点表示法 )。

例如,考虑具有以下形式的文档的集合userdata :

{ _id: 1, user: { login: "tester" } }

该集合具有以下索引:

{ "user.login": 1 }
{ "user.login": 1 }

索引将涵盖以下查询:

db.userdata.find( { "user.login": "tester" }, { "user.login": 1, _id: 0 } )
(1).多键覆盖

从3.6开始,如果索引跟踪哪个或哪些字段导致索引为多键,则多键索引可以覆盖非数组字段上的查询。 MongoDB 3.4或更高版本在MMAPv1以外的存储引擎上创建的多键索引跟踪此数据。

多键索引不能覆盖数组字段上的查询。

(2).性能

由于索引包含查询所需的所有字段,因此MongoDB可以匹配查询条件并仅使用索引返回结果。

仅查询索引比查询索引之外的文档要快得多。 索引键通常小于它们编目的文档,索引通常可在RAM中使用或按顺序位于磁盘上。

(3).限制
a.索引字段的限制  

地理空间索引无法覆盖查询 。

多键索引不能覆盖数组字段上的查询(详见Multikey Covering)

b.对Sharded Collection的限制

从MongoDB 3.0开始,如果索引不包含分片键,则在针对mongos运行时,索引无法覆盖分片集合上的查询,以及_id索引的以下异常:如果对分片集合的查询仅指定条件在_id字段上并且仅返回_id字段,即使_id字段不是分片键, _id索引也可以在针对mongos运行时覆盖查询。

在以前的版本中,当针对mongos运行时,索引无法覆盖分片集合上的查询。

c.explain

要确定查询是否为覆盖查询,请使用db.collection.explain()或explain()方法并查看结果 。

db.collection.explain()提供有关其他操作执行的信息,例如db.collection.update() 。 有关详细信息,请参阅db.collection.explain() 。

4.评估当前操作的性能

(1).使用Database Profiler评估对数据库的操作

MongoDB提供了一个数据库分析器 ,它显示了针对数据库的每个操作的性能特征。 使用分析器查找运行缓慢的任何查询或写入操作。 例如,您可以使用此信息来确定要创建的索引。

从MongoDB 4.2开始,用于读/写操作的探查器条目和诊断日志消息(即mongod / mongos日志消息)包括:

a.queryHash有助于识别具有相同查询形状的慢查询。
b.queryHash有助于识别具有相同查询形状的慢查询。

从版本4.2开始(也可从4.0.6开始),副本集的辅助成员现在记录需要比慢速操作阈值更长的oplog条目 。 在REPL组件下的diagnostic log为辅助节点记录这些慢速oplog消息,其中applied op: <oplog entry> took <num>ms了文本applied op: <oplog entry> took <num>ms 。 这些缓慢的oplog条目仅取决于慢速操作阈值。 它们不依赖于日志级别(系统级别或组件级别),分析级别或慢速操作采样率。 探查器不捕获慢速oplog条目。

(2).使用db.currentOp()来评估mongod操作

db.currentOp()方法报告在mongod实例上运行的当前操作。

(3).使用explain来评估查询性能

cursor.explain()和db.collection.explain()方法返回有关查询执行的信息,例如为满足查询和执行统计信息而选择的索引MongoDB。 您可以在queryPlanner模式, executionStats模式或allPlansExecution模式下运行方法来控制返回的信息量。

例:要在查询匹配表达式{ a: 1 }文档的查询中使用cursor.explain() ,请在名为records的集合中使用类似于mongo shell中的以下操作:

db.records.find( { a: 1 } ).explain("executionStats")

从MongoDB 4.2开始,解释输出包括:

a.queryHash有助于识别具有相同查询形状的慢查询
b.planCacheKey为慢查询提供更深入的查询计划缓存

5.优化查询性能

(1).创建支持查询的索引

对于常见的查询,请创建索引 。 如果查询搜索多个字段,请创建复合索引 。 扫描索引比扫描集合快得多。 索引结构小于文档引用,并按顺序存储引用。

例,如果您有包含博客帖子的posts集合,并且您经常发出在author_name字段上排序的查询,则可以通过在author_name字段上创建索引来优化查询:

db.posts.createIndex( { author_name : 1 } )

索引还可以提高对常规排序的查询的效率。

例,如果您经常发出在timestamp字段上排序的查询,则可以通过在timestamp字段上创建索引来优化查询:

db.posts.createIndex( { timestamp : 1 } )

优化此查询:

db.posts.find().sort( { timestamp : -1 } )

因为MongoDB可以按升序和降序读取索引,所以单键索引的方向无关紧要。

索引支持查询,更新操作和聚合管道的某些阶段。

如果符合以下条件,则BinData类型的索引键可以更有效地存储在索引中:

a.二进制子类型值的范围为0-7或128-135
b.字节数组的长度为:0,1,2,3,4,5,6,7,8,10,12,14,16,20,24或32
(2).限制查询结果数以减少网络需求

MongoDB 游标以多个文档组的形式返回结果。 如果您知道所需的结果数,则可以通过发出limit()方法来减少对网络资源的需求。

这通常与排序操作结合使用。 例如,如果查询到posts集合只需要10个结果,则可以发出以下命令:

db.posts.find().sort( { timestamp : -1 } ).limit(10)
(3).仅返回必要字段的数据

当您只需要文档中的一部分字段时,只需返回所需的字段即可获得更好的性能:

例如,如果在查询到posts集合时,只需要timestamp , title , author和abstract字段,则可以发出以下命令:

db.posts.find( {}, { timestamp : 1 , title : 1 , author : 1 , abstract : 1} ).sort( { timestamp : -1 } )
(4).使用$hint选择特定索引

在大多数情况下, 查询优化器会为特定操作选择最佳索引; 但是,您可以使用hint()方法强制MongoDB使用特定索引。 使用hint()来支持性能测试, 或者在某些必须选择多个索引中包含的字段或字段的查询中使用。

(5).使用增量运算符执行操作服务器端

使用MongoDB的inc运算符来增加或减少文档中的值。操作员增加服务器端字段的值,作为选择文档的替代方法,在客户端中进行简单修改,然后将整个文档写入服务器。

inc运算符还可以帮助避免竞争条件,这将导致两个应用程序实例查询文档,手动递增字段并同时保存整个文档。  

6.写操作性能

(1).索引

集合上的每个索引都会增加写入操作性能的开销。

对于集合上的每个insert或delete写入操作,MongoDB会从目标集合中的每个索引插入或删除相应的文档键。 update操作可能会导致更新集合上的索引子集,具体取决于受更新影响的keys。

注意:如果写入操作中涉及的文档包含在索引中,MongoDB仅更新稀疏或部分索引。

通常,索引为读取操作提供的性能增益值得插入惩罚。 但是,为了尽可能优化写入性能,在创建新索引和评估现有索引时要小心,以确保查询实际使用这些索引。

有关索引和查询,请参阅查询优化 。 有关索引的更多信息,请参阅索引和索引策略 。

(2).存储性能
硬件:

系统的功能为MongoDB的写操作的性能创建了一些重要的物理限制。 与驱动器存储系统相关的许多独特因素会影响写入性能,包括随机访问模式,磁盘高速缓存,磁盘预读和RAID配置。

对于随机工作负载,固态硬盘(SSD)的性能优于旋转硬盘(HDD)100倍或更多。

Journaling:

为了在发生崩溃时提供持久性,MongoDB使用写入日志记录到磁盘日志 。 MongoDB首先将内存中的更改写入磁盘日志文件。 如果在将更改提交到数据文件之前MongoDB应该终止或遇到错误,MongoDB可以使用日志文件将写入操作应用于数据文件。

虽然日志提供的持久性保证通常大于额外写入操作的性能成本,但请考虑日志和性能之间的以下相互作用:

a.如果日志和数据文件保存在同一块设备上,则数据文件和日志可能必须争用有限数量的可用I/O资源。 将日志移动到单独的设备可能会增加写入操作的容量。
b.如果应用程序指定了包含j选项的写入关注点,mongod将减少日志写入之间的持续时间,这会增加总体写入负载。
c.日志写入之间的持续时间可以使用commitIntervalms运行时选项进行配置。减少日志提交之间的时间间隔将增加写入操作的数量,这会限制MongoDB的写入操作能力。增加日志提交之间的时间量可能会减少写入操作的总数,但也会增加日志在发生故障时不会记录写入操作的可能性。