查询优化

索引通过减少查询操作需要处理的数据量来提高读取操作的效率。

这简化了与在 mongodb 中完成查询相关的工作。

创建索引以支持读取操作

如果应用程序在特定字段或一组字段上查询集合, 则查询字段上的索引或一组字段上的复合索引可能会阻止查询扫描整个集合以查找和返回查询结果。

案例

应用程序在类型字段上查询清单集合。

type 字段的值是用户驱动的。

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

若要提高此查询的性能, 请在类型字段上的库存集合中添加升序或降序索引。

在 mongo shell 中, 可以使用 db.collection.createIndex() 方法创建索引:

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

此索引可以防止上述类型查询扫描整个集合以返回结果。

索引支持排序

除了优化读取操作外, 索引还可以支持排序操作, 并允许更高效的存储利用率。

对于单场索引, 升序和降序之间的选择是不重要的。

对于复合索引, 选择很重要。(只要保证和索引顺序一致,或者刚好相反都可以命中索引。其他情况则不行)

查询选择性

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

更有选择性的查询与较小比例的文档匹配。

例如, 唯一 _id 字段上的相等匹配具有很强的选择性, 因为它最多可以匹配一个文档。

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

例如, $nin 和 $ne 的不等式运算符不是很有选择性, 因为它们往往与指数的很大一部分匹配。

因此, 在许多情况下, 具有索引的 $nin 或 $ne 查询的执行效果可能不比必须扫描集合中所有文档的 $nin 或 $ne 查询更好。

正则表达式的选择性取决于表达式本身。

有关详细信息, 请参阅 正则表达式和索引使用

覆盖的查询

覆盖的查询是可以完全使用索引来满足的查询, 无需检查任何文档。

当同时应用以下两个选项时, 索引涵盖查询:

查询中的所有字段都是索引的一部分, 并且结果中返回的所有字段都在同一个索引中。

例如, 集合清单在类型和项字段上具有以下索引:

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

此索引将涵盖以下对类型和项字段进行查询的操作, 并只返回项字段:

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

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

变化实例

在3.6 版中更改: 索引可以涵盖对嵌入文档中的字段的查询。

例如, 考虑具有以下形式的文档的集合用户数据:

{ _id: 1, user: { login: "tester" } }
  • 索引
{ "user.login": 1 }
  • 命中下面的覆盖查询
db.userdata.find( { "user.login": "tester" }, { "user.login": 1, _id: 0 } )

多键覆盖

从3.6 开始, 如果索引跟踪哪个或多个字段导致索引是多键的, 则多键索引可以覆盖对非数组字段的查询。

在 mongodb 3.4 或更高版本的存储引擎上创建的多键索引 (mmapv1 除外) 跟踪此数据。

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

性能

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

仅查询索引比查询索引之外的文档要快得多。

索引键通常小于它们所目录的文档, 索引通常在 ram 中可用, 或者按顺序位于磁盘上。

限制

  1. 对索引字段的限制

  2. 地理空间索引不能覆盖查询。

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

Restrictions on Sharded Collection

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

在以前的版本中, 索引在对 mongos 运行时不能覆盖对共享集合的查询。

评估当前操作的性能

以下部分描述了评估运营绩效的技术。

使用Database Profiler评估针对数据库的操作

MongoDB提供了一个数据库分析器,它显示了针对数据库的每个操作的性能特征。使用分析器查找运行缓慢的任何查询或写入操作。

例如,您可以使用此信息来确定要创建的索引。

有关更多信息,请参阅数据库分析。

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

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

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

cursor.explain() 和db.collection.explain()方法返回有关查询执行的信息,例如为满足查询和执行统计信息而选择的索引MongoDB。

您可以在queryPlanner模式,executionStats模式或allPlansExecution模式下运行方法来控制返回的信息量。

限制查询结果的数量以减少网络需求

mongodb 游标以多个文档的组返回结果。

如果您知道所需的结果数, 则可以通过颁发 limit() 方法来减少对网络资源的需求。

这通常与排序操作一起使用。

例如, 如果您只需要从查询到帖子集合的10个结果, 则需要发出以下命令:

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

只查询必须的字段

当您只需要文档中的一个字段子集时, 只需返回所需的字段, 就可以获得更好的性能:

例如, 如果在对帖子集合的查询中, 只需要时间戳、标题、作者和抽象字段, 则需要发出以下命令:

db.posts.find( {}, { timestamp : 1 , title : 1 , author : 1 , abstract : 1} ).sort( { timestamp : -1 } )

使用 $hint 选择特定索引

在大多数情况下, 查询优化器为特定操作选择最佳索引;

在大多数情况下, 查询优化器为特定操作选择最佳索引。

但是, 您可以使用 hint() 方法强制 mongodb 使用特定索引。

使用 hint() 支持性能测试, 或者在某些查询上, 您必须选择包含在多个索引中的字段。

使用增量运算符执行操作服务器端

使用 mongodb 的 $inc 运算符来递增或递减文档中的值。

操作员在服务器端递增字段的值, 作为选择文档的替代方法, 在客户端中进行简单修改, 然后将整个文档写入服务器。

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

写操作性能

索引

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

对于集合上的每个插入或删除写入操作,MongoDB会从目标集合中的每个索引插入或删除相应的文档键。

更新操作可能会导致对集合上的索引子集进行更新,具体取决于受更新影响的密钥。

注意

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

在使用MMAPv1存储引擎的mongod实例上,更新操作可能会导致文档超出其分配的空间。

当文档超出其分配的空间时,MMAPv1将文档移动到磁盘上的新位置,并且必须更新集合上的每个索引以指向新文档位置。这些移动操作可能很昂贵,但很少发生。

通常,索引为读取操作提供的性能增益值得插入惩罚。

但是,为了在可能的情况下优化写入性能,在创建新索引和评估现有索引时要小心,以确保查询实际使用这些索引。

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

文档增长和MMAPv1存储引擎

一些更新操作可以增加文档的大小;

例如,如果更新向文档添加新字段。

对于MMAPv1存储引擎,如果更新操作导致文档超出当前分配的记录大小,MongoDB会将文档重定位到磁盘上,并留有足够的连续空间来保存文档。需要重定位的更新比没有重定位的更新花费的时间更长,特别是在集合具有索引的情况下。

如果集合具有索引,MongoDB必须更新所有索引条目。

因此,对于具有许多索引的集合,此移动将影响写入吞吐量。

版本3.0.0中已更改:默认情况下,MongoDB使用Power of 2 Sized Allocations为MMAPv1存储引擎自动添加填充。

2的次幂大小分配确保MongoDB分配大小为2的大小的文档空间,这有助于确保MongoDB可以有效地重用文档删除或重定位创建的可用空间,并在许多情况下减少重新分配的发生。

虽然2大小分配的权力最小化重新分配的发生,但它不会消除文档重新分配。

有关更多信息,请参阅 MMAPv1存储引擎

存储性能

硬件

存储系统的功能为MongoDB的写操作性能创建了一些重要的物理限制。

与驱动器存储系统相关的许多独特因素会影响写入性能,包括随机访问模式,磁盘高速缓存,磁盘预读和RAID配置。

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

日记

为了在发生崩溃时提供持久性,MongoDB使用写入日志记录到磁盘日志。

MongoDB首先将内存中的更改写入磁盘日志文件。

如果MongoDB在提交对数据文件的更改之前应该终止或遇到错误,MongoDB可以使用日志文件将写入操作应用于数据文件。

虽然期刊提供的持久性保证通常会超过额外写入操作的性能成本,但请考虑期刊与绩效之间的以下相互作用:

如果日志和数据文件驻留在同一块设备上,则数据文件和日志可能必须争用有限数量的可用 I/O 资源。

将日志移动到单独的设备可能会增加写入操作的容量。

如果应用程序指定包含j选项的写入问题,则mongod将减少日志写入之间的持续时间,这会增加整体写入负载。

可以使用commitIntervalMs运行时选项配置日志写入之间的持续时间。

减少日志提交之间的时间间隔将增加写入操作的数量,这可能会限制MongoDB的写入操作容量。

增加日志提交之间的时间量可能会减少写入操作的总数,但也会增加日志在发生故障时不记录写入操作的可能性。

有关日记功能的其他信息,请参阅日记功能

参考资料

Query Optimize