复合索引引用多个字段,可以显着缩短查询响应时间。
在大多数情况下,应用 ESR(相等、排序、范围)准则来排列索引键可创建更高效的复合索引。
确保相等字段始终排在第一位。将相等字段放在第一位可以使其余索引字段按排序顺序排列。接下来,根据索引的特定需求,选择使用排序字段还是范围字段:
如果避免内存中排序很重要,请将排序字段放在范围字段之前 (ESR)
如果查询中的范围谓词具有很强的选择性,请将其放在排序字段 (ERS) 之前
有关优化查询的更多信息,请参阅 explain 和查询计划。
提示
要强制MongoDB使用特定索引,请在测试索引时使用游标.hint()(mongosh方法)。
相等
本页上的示例使用 sample_mflix示例数据集 中的数据。有关如何将此数据集加载到自管理MongoDB 部署中的详细信息,请参阅加载示例数据集。如果对示例数据库进行了任何修改,则可能需要删除并重新创建数据库才能运行本页上的示例。
“相等”系指单个值的精确匹配。以下精确匹配查询扫描 movies 集合以查找 title 字段 Equilibrium 精确匹配的文档。
db.movies.find( { title: "Equilibrium" }, { title: 1, year: 1, cast: 1 } )
[ { _id: ObjectId('573a13a3f29313caabd0c8d2'), year: 2002, title: 'Equilibrium', cast: [ 'Christian Bale', 'Dominic Purcell', 'Sean Bean', 'Christian Kahrmann' ] } ]
db.movies.find( { title: { $eq: "Equilibrium" } }, { title: 1, year: 1, cast: 1 } )
[ { _id: ObjectId('573a13a3f29313caabd0c8d2'), year: 2002, title: 'Equilibrium', cast: [ 'Christian Bale', 'Dominic Purcell', 'Sean Bean', 'Christian Kahrmann' ] } ]
索引搜索可有效利用精确匹配来减少检查的索引键数量。相等字段必须放在第一位。
一个索引可以有多个相等键。它们可以以任何相对彼此的顺序出现,但所有相等键必须位于任何排序或范围字段之前。
等值匹配的选择性越强,索引查询的效率就越高。
Sort
“排序”确定结果的顺序。要避免内存中排序,请将排序字段放在索引中的范围之前。
仅当查询在排序键之前的所有前缀键上包含相等条件时,索引支持对其键的子集进行排序操作。有关详细信息,请参阅索引的排序和非前缀子集。
以下示例查询 movies集合中 directors字段包含 "David Lynch" 的电影。输出按 year 排序:
db.movies.find( { directors: "David Lynch" }, { title: 1, year: 1 } ).sort( { year: 1 } )
[ { _id: ObjectId('573a1397f29313caabce77d4'), title: 'The Elephant Man', year: 1980 }, { _id: ObjectId('573a1398f29313caabce9091'), title: 'Dune', year: 1984 }, { _id: ObjectId('573a1398f29313caabce9e12'), title: 'Blue Velvet', year: 1986 }, { _id: ObjectId('573a1399f29313caabced630'), year: 1992, title: 'Twin Peaks: Fire Walk with Me' }, { _id: ObjectId('573a139af29313caabcf00f0'), year: 1997, title: 'Lost Highway' }, { _id: ObjectId('573a139ef29313caabcfbc0e'), year: 1999, title: 'The Straight Story' }, { _id: ObjectId('573a139ef29313caabcfbc36'), title: 'Mulholland Drive', year: 2001 }, { _id: ObjectId('573a13b4f29313caabd40a54'), title: 'Inland Empire', year: 2006 } ]
要提高查询性能,请对 directors 和 year 字段创建索引:
db.movies.createIndex( { directors: 1, year: 1 } )
directors是第一个键,因为它是相等匹配。year按照与查询相同的顺序 (1) 建立索引。
范围
“范围”过滤器会扫描字段。此扫描不要求精确匹配,因此范围过滤器会松散绑定到索引键。为提高查询效率,应尽可能缩小范围边界,并使用等值匹配来限制必须扫描的文档数量。
范围筛选器类似如下内容:
db.movies.find( { runtime: { $gte: 1000 } }, { title: 1, runtime: 1, year: 1, plot: 1 } )
[ { _id: ObjectId('573a1397f29313caabce69db'), plot: 'The economic and cultural growth of Colorado spanning two centuries from the mid-1700s to the late-1970s.', runtime: 1256, title: 'Centennial', year: 1978 }, { _id: ObjectId('573a1399f29313caabcee1aa'), plot: 'A documentary on the history of the sport with major topics including Afro-American players, player/team owner relations and the resilience of the game.', runtime: 1140, title: 'Baseball', year: 1994 } ]
db.movies.find( { year: { $lt: 1900 } }, { title: 1, year: 1, plot: 1 } )
[ { _id: ObjectId('573a139cf29313caabcf560f'), plot: 'Two people kiss.', title: 'The Kiss', year: 1896 }, { _id: ObjectId('573a13a0f29313caabd041db'), plot: 'Two people kiss.', title: 'The Kiss', year: 1896 } ]
db.movies.find( { type: { $ne: "movie" } }, { title: 1, year: 1, type: 1 } )
[ { _id: ObjectId('573a1395f29313caabce2f03'), title: 'The Forsyte Saga', year: 1967, type: 'series' }, { _id: ObjectId('573a1396f29313caabce520d'), title: 'Scenes from a Marriage', year: 1973, type: 'series' }, { _id: ObjectId('573a1396f29313caabce5b86'), title: 'Ironiya sudby, ili S legkim parom!', year: 1975, type: 'series' }, { _id: ObjectId('573a1397f29313caabce6378'), title: 'Sybil', year: 1976, type: 'series' }, { _id: ObjectId('573a1397f29313caabce6443'), title: 'Jesus of Nazareth', year: 1977, type: 'series' } ]
如果查询中的范围谓词选择性很强,请将其放在排序字段之前,以减少已排序文档的数量并允许进行内存中排序。
要避免内存中排序,请将范围过滤放在排序谓词之后。有关内存中排序的更多信息,请参阅 cursor.allowDiskUse()。
其他注意事项
$regex是个范围操作符。$in单独使用时,它是一个执行一系列相等匹配的相等运算符。当
$in与.sort()一起使用时:如果
$in的大量元素少于 201 个,则使用SORT_MERGE阶段,按照为索引指定的排序顺序展开并合并这些元素。这提高了小型数组的性能。在本例中,$in类似于具有 ESR 的相等谓词。如果
$in具有 201 个或更多元素,则这些元素的排序方式类似于范围操作符。在这种情况下,小型数组的性能提升无法实现。索引中的后续字段不可能用于排序,并且$in类似于具有 ESR 的范围谓词。如果您通常将
$in操作符用于小型数组,请将其尽早包含在索引规范中。如果您通常使用大型数组,请在需要范围谓词的位置加入$in操作符。
注意
对于所有MongoDB版本,不能保证 201大量元素处的 $in 行为更改保持不变。
例子
以下查询在 movies集合中搜索 directors字段为 "David Lynch" 且 runtime字段小于 130 分钟的电影。结果按 year 排序:
db.movies.find( { directors: "David Lynch", runtime: { $lt: 130 } }, { title: 1, year: 1, runtime: 1 } ).sort( { year: 1 } )
[ { _id: ObjectId('573a1397f29313caabce77d4'), runtime: 124, title: 'The Elephant Man', year: 1980 }, { _id: ObjectId('573a1398f29313caabce9e12'), runtime: 120, title: 'Blue Velvet', year: 1986 }, { _id: ObjectId('573a139ef29313caabcfbc0e'), year: 1999, title: 'The Straight Story', runtime: 112 } ]
该查询包含 ESR 指南的所有内容:
directors: "David Lynch"是基于相等的匹配runtime: { $lt: 130 }是基于范围的匹配year用于排序
根据 ESR 指南,示例查询的最佳索引为:
{ directors: 1, year: 1, runtime: 1 }