部分索引
部分索引仅对集合中满足指定过滤表达式的文档进行索引。通过索引集合中的一部分文档,部分索引的存储要求更低,索引创建和维护的性能成本也更低。
创建部分索引
要创建 partial
索引,请使用 db.collection.createIndex()
方法并指定 partialFilterExpression
选项。partialFilterExpression
选项接受使用以下方式指定过滤条件的文档:
例如,以下操作创建一个复合索引,该索引仅对 rating
字段大于 5 的文档进行索引。
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
字段上包含 non-null 匹配项。
例如,以下查询可以使用该索引,因为它既在 name
字段上包含条件,又在 email
字段上包含 non-null 匹配:
db.contacts.find( { name: "xyz", email: { $regex: /\.org$/ } } )
但是,以下查询无法使用索引,因为该查询在 email
字段上包含 null 匹配,这是筛选器表达式 { email: { $exists: true } }
所不允许的。
db.contacts.find( { name: "xyz", email: { $exists: false } } )
部分 TTL 索引
部分索引也可以是 TTL 索引。部分 TTL 索引匹配指定的筛选器表达式,并且仅使那些文档过期。有关详情,请参阅使用筛选条件令文档过期。
限制
您不能同时指定
partialFilterExpression
选项和sparse
选项。_id
索引不能是部分索引。分片键索引不能为部分索引。
如果使用客户端字段级加密或 Queryable Encryption 加密,则
partialFilterExpression
不能引用加密字段。
等效索引
从MongoDB 7.3开始,您无法创建等效索引,即具有相同索引键和使用排序规则的相同部分表达式的部分索引。
对于 MongoDB 7.3 中已有等价索引的数据库,索引会保留,但查询时只使用第一个等价索引。这与 7.3 之前的 MongoDB 版本的行为相同。
有关示例,请参阅等效索引示例。
示例
在集合上创建部分索引
示例集合 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 中已有等价索引的数据库,索引会保留,但查询时只使用第一个等价索引。这与 7.3 之前的 MongoDB 版本的行为相同。
在以前的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
在7.3之前的MongoDB版本中,您可以创建索引,但这些查询仅使用第一个索引( 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 } )