查询优化通过减少查询操作需要进程的数据量来提高读取操作的效率。使用索引、投影和查询限制来提高查询性能并减少资源消耗。
创建索引以支持查询
为常用查询创建 索引。如果查询搜索多个字段,请创建复合索引。使用索引可提高性能,因为如果没有索引,查询必须扫描集合中的每个文档。
示例,考虑对 movies集合中的 rated字段执行以下查询:
let ratingValue = <someUserInput>; db.movies.find( { rated: ratingValue } );
要提高此查询的性能,请向 rated字段上的 movies集合添加一个索引。[1] 在 mongosh 中,使用 db.collection.createIndex() 方法创建索引:
db.movies.createIndex( { rated: 1 } )
要分析查询性能,请参阅解释解释计划结果。
| [1] | 对于单字段索引,索引的顺序无关紧要。对于复合索引,字段顺序会影响索引支持的查询。有关详情,请参阅 复合索引排序顺序。 |
创建选择性查询
查询选择性是指查询谓词筛选出集合中文档的程度。查询选择性决定了查询能否有效地使用索引。
选择性更强的查询匹配的文档比例更小。例如,唯一的 _id 字段上的相等匹配具有高度选择性,因为它最多可以匹配一个文档。
选择性较低的查询会匹配更大比例的文档,并且无法有效地使用索引。
例如,不等于操作符 $nin 和 $ne 的选择性不高,因为它们通常匹配很大一部分索引。因此,在许多情况下,带有索引的 $nin 或 $ne 查询的性能可能不会比必须扫描集合中所有文档的 $nin 或 $ne 查询更好。
regular expressions 的选择性取决于表达式本身。有关详情,请参阅正则表达式和索引使用。
仅投影必要的数据
当您需要文档中的部分字段时,可以通过仅返回所需的字段来提高性能。投影可减少网络流量和处理时间。
示例,如果对 movies集合的查询仅需要 year、title、directors 和 plot 字段,请在投影中指定这些字段:
db.movies.find( {}, { year: 1, title: 1, directors: 1 } ).sort( { year: -1 } ).limit(3)
当您使用$project聚合阶段时,它通常应该是管道中的最后一个阶段,用于指定要返回给客户端的字段。
在管道的开头或中间使用 $project 阶段来减少传递到后续管道阶段的字段数量不太可能提高性能,因为数据库会自动执行此优化。
有关使用投影的更多信息,请参阅投影要从查询返回的字段。
例子
要实现覆盖查询,必须对投影字段索引。ESR(相等、排序、范围)规则适用于索引中字段的顺序。
例如,考虑 movies集合上的以下索引:
db.movies.createIndex( { rated: 1, _id: 1, "imdb.rating": 1, title: 1, released: 1 } )
上述索引在技术上是正确的,但其结构并未优化查询性能。
以下查询使用 ESR(相等、排序、范围)规则构建更高效的聚合管道并缩短查询响应时间。
db.movies.aggregate( [ { $match: { rated: "PG", released: { $gt: ISODate("2000-01-01T00:00:00Z") } } }, { $sort: { title: 1 } }, { $limit: 5 }, { $project: { _id: 1, "imdb.rating": 1 } } ] )
索引和查询遵循 ESR 规则:
rated用于等值匹配(E),因此它是索引}中的第一个字段。title用于排序 (S),因此在索引中位于rated之后。released用于范围查询(R ),因此它是索引中的最后一个字段。
限制查询结果
MongoDB 游标 分批返回结果。如果您知道所需的结果数,请在 limit() 方法中指定该值。限制结果可减少对网络资源的需求。
在应用限制之前对结果进行排序,以确保查询返回预期的文档。例如,如果您只需要从对 movies 集合的查询中获取 10 个结果,请运行以下查询:
db.movies.find( {}, { title: 1, year: 1 } ).sort( { year: -1 } ).limit(10)
有关限制结果的更多信息,请参阅 limit()。
使用索引提示
查询优化器通常会为特定操作选择最佳索引。但是,您可以使用 hint() 方法强制 MongoDB 使用特定索引。使用 hint() 支持性能测试,或在查询出现在多个索引中的字段时,确保 MongoDB 使用正确索引。
使用服务器端操作
使用 $inc 操作符增加或减少文档中的值。作为选择文档、在客户端代码中进行更改然后将整个文档写入服务器的替代方法,此操作符在服务器端增加字段的值。$inc 操作符还有助于避免竞争条件;当两个应用程序实例查询文档、手动增加字段并同时保存整个文档时,便会出现竞争条件。
运行涵盖的查询
覆盖查询是可以完全使用索引来满足并且不必检查任何文档的查询。满足以下所有条件时,索引将覆盖查询:
查询中的所有字段(包括应用程序指定的字段和内部需要的字段,例如出于分片目的)都是索引的一部分。
结果中返回的所有字段都位于同一索引中。
查询中没有字段等于
null。例如,以下查询谓词无法生成覆盖查询:{ "field": null }{ "field": { $eq: null } }
例子
movies集合在 rated 和 title 字段上具有以下索引:
db.movies.createIndex( { rated: 1, title: 1 } )
该索引涵盖查询 rated 和 title 字段并仅返回 title 字段的以下操作:
db.movies.find( { rated: "PG", title: /^T/ }, { title: 1, _id: 0 } ).limit(3)
为了使指定索引覆盖查询,投影文档必须显式指定 _id: 0 以从结果中排除 _id 字段,因为索引不包含 _id 字段。
嵌入式文档
索引可以涵盖对嵌入式文档中字段的查询。
例如,考虑 sample_mflix 数据集中的 theaters 集合,其中的文档具有以下结构:
{ theaterId: <num>, location: { address: { street1: "<address>", city: "<city>", state: "<state>", zipcode: "<zip>" }, geo: { ... } } }
该集合包含以下索引:
db.theaters.createIndex( { "location.address.city": 1 } )
{ "location.address.city": 1 } 索引涵盖以下查询:
db.theaters.find( { "location.address.city": "Portland" }, { "location.address.city": 1, _id: 0 } ).limit(1)
注意
要为嵌入式文档中的字段编制索引,请使用点表示法。请参阅在嵌入式字段上创建索引。
多键覆盖
多键索引如果要追踪哪个或哪些字段致使其成为多键,则其可涵盖对非数组字段的查询。
多键索引无法涵盖对数组字段的查询。
有关具有多键索引的覆盖查询的示例,请参阅多键索引页面上的覆盖查询。
性能
由于索引包含查询所需的所有字段,因此,MongoDB 可以只使用索引来匹配查询条件并返回结果。
仅查询索引比查询索引之外的文档要快得多。索引键通常小于它们编目的文档,并且索引通常在 RAM 中可用或按顺序存储在磁盘上。
限制
索引类型
并非所有索引类型都可以覆盖查询。有关覆盖索引支持的详细信息,请参阅相应索引类型的文档页面。
分片集合
解释结果
要确定查询是否属于覆盖查询,请使用 db.collection.explain() 或 explain() 方法。请参阅覆盖查询。