查询优化通过减少查询操作需要进程的数据量来提高读取操作的效率。使用索引、投影和查询限制来提高查询性能并减少资源消耗。
创建索引以支持查询
为常用查询创建 索引。如果查询搜索多个字段,请创建复合索引。使用索引可提高性能,因为如果没有索引,查询必须扫描集合中的每个文档。
示例,考虑对 inventory
集合中的 type
字段执行以下查询:
let typeValue = <someUserInput>; db.inventory.find( { type: typeValue } );
要提高此查询的性能,请向inventory
字段上的type
集合添加一个索引。 []1 在mongosh
中,使用db.collection.createIndex()
方法创建索引:
db.inventory.createIndex( { type: 1 } )
[1] | 对于单字段索引,索引的顺序并不重要。对于复合索引,字段顺序会影响索引支持的查询。有关详细信息,请参阅复合索引排序顺序。 |
创建选择性查询
查询选择性是指查询谓词筛选出集合中文档的程度。查询选择性决定了查询能否有效地使用索引。
选择性更强的查询匹配的文档比例更小。例如,唯一的 _id
字段上的相等匹配具有高度选择性,因为它最多可以匹配一个文档。
选择性较低的查询会匹配更大比例的文档,并且无法有效地使用索引。
例如,不等于操作符 $nin
和 $ne
的选择性不高,因为它们通常匹配很大一部分索引。因此,在许多情况下,带有索引的 $nin
或 $ne
查询的性能可能不会比必须扫描集合中所有文档的 $nin
或 $ne
查询更好。
regular expressions
的选择性取决于表达式本身。有关详情,请参阅正则表达式和索引使用。
仅投影必要的数据
当您需要文档中的部分字段时,可以通过仅返回所需的字段来提高性能。投影可减少网络流量和处理时间。
示例,如果对 posts
集合的查询仅需要 timestamp
、title
、author
和 abstract
字段,请在投影中指定这些字段:
db.posts.find( {}, { timestamp : 1, title : 1, author : 1, abstract : 1} ).sort( { timestamp : -1 } )
当您使用$project
聚合阶段时,它通常应该是管道中的最后一个阶段,用于指定要返回给客户端的字段。
在管道的开头或中间使用 $project
阶段来减少传递到后续管道阶段的字段数量不太可能提高性能,因为数据库会自动执行此优化。
有关使用投影的更多信息,请参阅投影要从查询返回的字段。
限制查询结果
MongoDB游标分批返回结果。如果您知道所需的结果数,请在 limit()
方法中指定该值。限制结果可减少对网络资源的需求。
通常,在对结果进行排序时,限制结果非常有用,这样您就知道将返回哪些文档。示例,如果您只需要 posts
集合的查询中的 10 个结果,运行以下查询:
db.posts.find().sort( { timestamp : -1 } ).limit(10)
有关限制结果的更多信息,请参阅limit()
。
使用索引提示
查询优化器通常会为特定操作选择最佳索引。但是,您可以使用 方法强制MongoDB使用特定索引。使用hint()
hint()
支持性能测试,或者当您查询出现在多个索引中的字段时,以保证MongoDB使用正确的索引。
使用服务器端操作
使用 MongoDB 的$inc
操作符递增或递减文档中的值。该操作符会在服务器端递增该字段的值,作为选择文档的替代方法,在客户端进行简单修改,然后将整个文档写入服务器。$inc
操作符还有助于避免在两个应用程序实例查询文档、手动递增字段以及同时保存回整个文档时出现的争用情况。
运行涵盖的查询
覆盖查询是可以完全使用索引来满足并且不必检查任何文档的查询。满足以下所有条件时,索引将覆盖查询:
查询中的所有字段(包括应用程序指定的字段和内部需要的字段,例如出于分片目的)都是索引的一部分。
结果中返回的所有字段都位于同一索引中。
查询中没有字段等于
null
。例如,以下查询谓词无法生成覆盖查询:{ "field": null }
{ "field": { $eq: null } }
例子
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
字段。
嵌入式文档
索引可以涵盖对嵌入式文档中字段的查询。
例如,考虑一个包含以下形式文档的 userdata
集合:
db.userdata.insertOne( { _id: 1, user: { login: "tester" } } )
该集合包含以下索引:
db.userdata.createIndex( { "user.login": 1 } )
{ "user.login": 1 }
索引涵盖以下查询:
db.userdata.find( { "user.login": "tester" }, { "user.login": 1, _id: 0 } )
注意
要为嵌入式文档中的字段编制索引,请使用点表示法。请参阅在嵌入式字段上创建索引。
多键覆盖
多键索引如果要追踪哪个或哪些字段致使其成为多键,则其可涵盖对非数组字段的查询。
多键索引无法涵盖对数组字段的查询。
有关具有多键索引的覆盖查询的示例,请参阅多键索引页面上的覆盖查询。
性能
由于索引包含查询所需的所有字段,因此,MongoDB 可以只使用索引来匹配查询条件并返回结果。
仅查询索引比查询索引之外的文档要快得多。索引键通常小于它们编目的文档,并且索引通常在 RAM 中可用或按顺序存储在磁盘上。
限制
索引类型
并非所有索引类型都可以覆盖查询。有关覆盖索引支持的详细信息,请参阅相应索引类型的文档页面。
分片集合
解释结果
要确定查询是否属于覆盖查询,请使用 db.collection.explain()
或 explain()
方法。请参阅覆盖查询。