部分索引
部分索引只对满足指定过滤表达式的集合中的文档进行索引。通过索引集合中的子集文档,部分索引具有更低的存储需求,并且在创建和维护索引方面的性能成本更低。
创建部分索引
要创建一个部分
索引,使用带有partialFilterExpression
选项的db.collection.createIndex()
方法。该partialFilterExpression
选项接受一个文档,该文档使用等式表达式(即field: value
或使用$eq
运算符)指定过滤器条件
例如,以下操作创建了一个复合索引,仅对具有大于5的rating
字段的文档进行索引。
db.restaurants.createIndex( { cuisine: 1, name: 1 }, { partialFilterExpression: { rating: { $gt: 5 } } } )
您可以为所有MongoDB索引类型指定partialFilterExpression
选项。当为时间序列集合上的TTL索引指定partialFilterExpression
时,您只能对集合的metaField
进行筛选。
行为
查询覆盖率
如果使用索引会导致结果集不完整,MongoDB不会在查询或排序操作中使用部分索引。
要使用部分索引,查询必须包含过滤器表达式(或修改后的过滤器表达式,该表达式指定了过滤器表达式的一个子集)作为其查询条件的一部分。
例如,给定以下索引
db.restaurants.createIndex( { cuisine: 1 }, { partialFilterExpression: { rating: { $gt: 5 } } } )
以下查询可以使用索引,因为查询谓词包括条件 rating: { $gte: 8 }
,该条件与索引过滤器表达式 rating: { $gt: 5 }
匹配的文档子集。
db.restaurants.find( { cuisine: "Italian", rating: { $gte: 8 } } )
但是,以下查询不能在 cuisine
字段上使用部分索引,因为使用索引会导致结果集不完整。具体来说,查询谓词包括条件 rating: { $lt: 8 }
,而索引的过滤器是 rating: { $gt: 5 }
。也就是说,查询 { cuisine: "Italian", rating: { $lt: 8 } }
匹配的文档(例如,评分为1的意大利餐厅)比索引中多。
db.restaurants.find( { cuisine: "Italian", rating: { $lt: 8 } } )
同样,以下查询不能使用部分索引,因为查询谓词不包含过滤器表达式,使用索引将返回不完整的结果集。
db.restaurants.find( { cuisine: "Italian" } )
与稀疏索引的比较
应优先考虑部分索引而非稀疏索引。部分索引提供以下优势
对哪些文档被索引有更大的控制。
包含稀疏索引提供的所有功能。
稀疏索引根据索引字段的存在性选择要索引的文档,对于复合索引,则是索引字段的存在性。
部分索引根据指定的过滤器确定索引条目。过滤器可以包括除了索引键之外的字段,并且可以指定除存在性检查之外的条件。例如,部分索引可以实现与稀疏索引相同的行为
db.contacts.createIndex( { name: 1 }, { partialFilterExpression: { name: { $exists: true } } } )
此部分索引支持与稀疏索引在name
字段上相同的查询。
但是,部分索引还可以在除索引键之外的字段上指定过滤器表达式。例如,以下操作创建了一个部分索引,其中索引在name
字段上,但过滤器表达式在email
字段上
db.contacts.createIndex( { name: 1 }, { partialFilterExpression: { email: { $exists: true } } } )
为了查询优化器选择此部分索引,查询谓词必须包含对name
字段的条件以及email
字段的非空匹配。
例如,以下查询可以使用索引,因为它包含对name
字段的条件以及对email
字段的非空匹配
db.contacts.find( { name: "xyz", email: { $regex: /\.org$/ } } )
但是,以下查询无法使用索引,因为它包含对email
字段的空匹配,这不符合过滤器表达式{ email: { $exists: true } }
db.contacts.find( { name: "xyz", email: { $exists: false } } )
部分TTL索引
部分索引也可以是TTL索引。部分TTL索引匹配指定的过滤器表达式,并仅使那些文档过期。有关详细信息,请参阅使用过滤器条件过期文档。
限制
您不能同时指定
partialFilterExpression
选项和sparse
选项。_id
索引不能是部分索引。分片键索引不能是部分索引。
等效索引
从MongoDB 7.3开始,您不能创建等效索引,这些索引是具有相同索引键和相同部分表达式(使用排序规则)的部分索引。
对于MongoDB 7.3中具有现有等效索引的数据库,索引将被保留,但在查询中仅使用第一个等效索引。这与MongoDB 7.3之前的版本的行为相同。
示例请参阅等效索引示例.
示例
在集合上创建部分索引
考虑一个包含类似以下文档的集合 restaurants
{ "_id" : ObjectId("5641f6a7522545bc535b5dc9"), "address" : { "building" : "1007", "coord" : [ -73.856077, 40.848447 ], "street" : "Morris Park Ave", "zipcode" : "10462" }, "borough" : "Bronx", "cuisine" : "Bakery", "rating" : { "date" : ISODate("2014-03-03T00:00:00Z"), "grade" : "A", "score" : 2 }, "name" : "Morris Park Bake Shop", "restaurant_id" : "30075445" }
您可以在 borough
和 cuisine
字段上添加部分索引,只对那些 rating.grade
字段为 A
的文档进行索引
db.restaurants.createIndex( { borough: 1, cuisine: 1 }, { partialFilterExpression: { 'rating.grade': { $eq: "A" } } } )
然后,对 restaurants
集合的以下查询使用部分索引以返回布朗克斯的 rating.grade
等于 A
的餐馆
db.restaurants.find( { borough: "Bronx", 'rating.grade': "A" } )
但是,以下查询无法使用部分索引,因为查询谓词中不包括 rating.grade
字段
db.restaurants.find( { borough: "Bronx", cuisine: "Bakery" } )
具有唯一约束的部分索引
部分索引只对满足特定过滤表达式的文档进行索引。如果您同时指定了 partialFilterExpression
和 唯一约束,则唯一约束仅适用于满足过滤表达式的文档。具有唯一约束的部分索引不会阻止插入不满足唯一约束的文档,如果这些文档不满足过滤标准。
例如,一个包含以下文档的集合 users
{ "_id" : ObjectId("56424f1efa0358a27fa1f99a"), "username" : "david", "age" : 29 } { "_id" : ObjectId("56424f37fa0358a27fa1f99b"), "username" : "amanda", "age" : 35 } { "_id" : ObjectId("56424fe2fa0358a27fa1f99c"), "username" : "rajiv", "age" : 57 }
以下操作创建了一个索引,该索引指定了在 唯一约束 上对 username
字段和一个部分过滤器表达式 age: { $gte: 21 }
。
db.users.createIndex( { username: 1 }, { unique: true, partialFilterExpression: { age: { $gte: 21 } } } )
该索引阻止了以下文档的插入,因为这些文档已经存在具有指定的用户名,并且 age
字段的值大于 21
。
db.users.insertMany( [ { username: "david", age: 27 }, { username: "amanda", age: 25 }, { username: "rajiv", age: 32 } ] )
然而,以下具有重复用户名的文档是允许的,因为唯一约束仅适用于 age
大于或等于 21 的文档。
db.users.insertMany( [ { username: "david", age: 20 }, { username: "amanda" }, { username: "rajiv", age: null } ] )
等效索引示例
从MongoDB 7.3开始,您不能创建等效索引,这些索引是具有相同索引键和相同部分表达式(使用排序规则)的部分索引。
对于MongoDB 7.3中具有现有等效索引的数据库,索引将被保留,但在查询中仅使用第一个等效索引。这与MongoDB 7.3之前的版本的行为相同。
在之前的 MongoDB 版本中,您可以创建两个等效索引。以下示例创建了一个名为 pizzas
的集合和两个等效索引,分别命名为 index0
和 index1
。
// Create the pizzas collection db.pizzas.insertMany( [ { _id: 0, type: "pepperoni", size: "small", price: 4 }, { _id: 1, type: "cheese", size: "medium", price: 7 }, { _id: 2, type: "vegan", size: "large", price: 8 } ] ) // Create two equivalent indexes with medium pizza sizes db.pizzas.createIndex( { type: 1 }, { name: "index0", partialFilterExpression: { size: "medium" }, collation: { locale: "en_US", strength: 1 } } ) db.pizzas.createIndex( { type: 1 }, { name: "index1", partialFilterExpression: { size: "MEDIUM" }, collation: { locale: "en_US", strength: 1 } } )
这两个索引是等效的,因为两个索引指定了相同的披萨大小,只是在部分过滤器表达式的文本格式上有所不同。查询仅使用一个索引:首先创建的索引,在之前的示例中是 index0
。
从 MongoDB 7.3 版本开始,您不能创建第二个索引(index1
),并且会返回此错误。
MongoServerError: Index already exists with a different name: index0
在 MongoDB 7.3 版本之前的版本中,您可以创建索引,但只有第一个索引(index0
)会与这些查询一起使用。
db.pizzas.find( { type: "cheese", size: "medium" } ).collation( { locale: "en_US", strength: 1 } ) db.pizzas.find( { type: "cheese", size: "MEDIUM" } ).collation( { locale: "en_US", strength: 1 } ) db.pizzas.find( { type: "cheese", size: "Medium" } ).collation( { locale: "en_US", strength: 1 } )