查询优化减少了查询必须进程的数据量。使用索引、投影和查询限制来提高性能并减少资源消耗。随着集合的增长,定期查看查询性能,以确定何时进行扩展。
创建索引以支持查询
索引将字段值存储在单独的数据结构中。在读取操作中, MongoDB搜索索引而不是扫描整个集合。在写入操作中, MongoDB同时更新集合和索引。
为最常见的查询创建索引。如果查询搜索多个字段,请创建复合索引。
示例,考虑对 inventory集合中的 type字段执行以下查询:
let typeValue = <someUserInput>; db.inventory.find( { type: typeValue } );
要提高此查询的性能,请在 type字段上添加索引。
db.inventory.createIndex( { type: 1 } )
[1] 在 mongosh 中,使用 db.collection.createIndex():
要分析查询性能,请参阅解释解释计划结果。
| [1] | 对于单字段索引,索引的顺序并不重要。对于复合索引,字段顺序会影响索引支持的查询。有关详细信息,请参阅复合索引排序顺序。 |
创建选择性查询
查询选择性衡量查询谓词过滤文档的程度,并确定查询是否可以有效地使用索引。
高选择性查询匹配更少的文档,并更有效地使用索引。实例,
_id上的等值匹配具有高度选择性,因为它最多可以匹配一个文档。选择性较低的查询会匹配更多文档,但会降低索引的使用效率。
例如,不等于操作符 $nin 和 $ne 的选择性不高,因为它们通常匹配很大一部分索引。因此,在许多情况下,带有索引的 $nin 或 $ne 查询的性能可能不会比必须扫描集合中所有文档的 $nin 或 $ne 查询更好。
regular expression 的选择性取决于表达式本身。有关详细信息,请参阅正则表达式和索引使用。
仅投影必要的数据
当您需要文档中的部分字段时,可以通过仅返回所需的字段来提高性能。投影可减少网络流量和处理时间。
示例,假设以下查询仅返回 timestamp、title、author 和 abstract 字段。
db.posts.find( {}, { timestamp : 1, title : 1, author : 1, abstract : 1} ).sort( { timestamp : -1 } )
当您使用$project聚合阶段时,它通常应该是管道中的最后一个阶段,用于指定要返回给客户端的字段。
在管道的开头或中间使用 $project 阶段来减少传递到后续管道阶段的字段数量不太可能提高性能,因为数据库会自动执行此优化。
有关详细信息,请参阅要从查询返回的项目字段。
例子
要实现覆盖查询,请对投影字段索引。ESR(相等、排序、范围)规则适用于索引中字段的顺序。
示例,考虑 inventory集合上的以下索引:
db.inventory.createIndex( { type: 1, _id: 1, price: 1, item: 1, expiryDate: 1} )
上述查询虽然在技术上是正确的,但其结构并未优化查询性能。
以下查询应用 ESR 规则以实现更高效的复合索引:
db.inventory.aggregate([ { $match: {type: "food", expiryDate: { $gt: ISODate("2025-07-10T00:00:00Z") }}}, { $sort: { item: 1 }}, { $project: { _id: 1, price: 1} } ])
索引和查询遵循 ESR 规则:
type用于等值匹配(E),因此它是索引}中的第一个字段。item用于排序 (S),因此在索引中位于type之后。expiryDate用于范围查询(R ),因此它是索引中的最后一个字段。
限制查询结果
MongoDB 游标 分批返回结果。如果知道需要多少结果,请将该值传递给 limit() 方法以减少网络资源使用量。
限制排序后的结果,以便您知道返回了哪些文档。示例,以下查询仅返回 posts集合中的 10 个最新结果:
db.posts.find().sort( { timestamp : -1 } ).limit(10)
有关更多信息,请参阅 limit()。
使用索引提示
查询优化器会为特定操作选择最佳索引。但是,您可以使用 hint() 方法强制执行特定索引。这对于性能测试或当一个字段出现在多个索引中并且您需要保证MongoDB使用哪个索引非常有用。
使用服务器端操作
使用 $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 } )
注意
要为嵌入式文档中的字段编制索引,请使用点表示法。请参阅在嵌入式字段上创建索引。
多键覆盖
如果索引追踪哪个或哪些字段使其成为多键,则多键索引可以涵盖对非数组字段的查询。
多键索引无法涵盖对数组字段的查询。
有关示例,请参阅多键索引页面上的涵盖查询。
性能
涵盖的查询匹配查询条件并仅使用索引返回结果。这比获取文档更快,因为索引键通常小于文档,并且索引通常位于RAM中或按顺序存储在磁盘上。
限制
索引类型
并非所有索引类型都支持覆盖查询。请参阅特定索引类型的文档。
分片集合
解释结果
要检查查询是否被覆盖,请使用 db.collection.explain() 或 explain()。请参阅涵盖的查询。