Unique Indexes
唯一索引确保索引字段不存储重复;即强制索引字段的唯一性。默认情况下,MongoDB 在创建集合期间会在 _id 字段上创建唯一索引。
注意
新的内部格式
从 MongoDB 4.2 开始,对于 4.2(或更高版本)的featureCompatibilityVersion (fCV),MongoDB 对唯一索引使用新的内部格式,该格式与早期 MongoDB 版本不兼容。新格式适用于现有的唯一索引以及新创建/重建的唯一索引。
您可以在用户界面中为 MongoDB Atlas 中托管的部署创建和管理唯一索引。
创建唯一索引
要创建唯一索引,请使用 db.collection.createIndex()
方法,并将 unique
选项设置为 true
。
db.collection.createIndex( <key and index type specification>, { unique: true } )
单个字段上的唯一索引
例如,要对 members
集合的 user_id
字段创建唯一索引,请在 mongosh
中使用以下操作:
db.members.createIndex( { "user_id": 1 }, { unique: true } )
唯一符合索引
还可以在复合索引上强制执行唯一性约束。如果对复合索引使用唯一性约束,则 MongoDB 将对索引键值的组合强制唯一性。
例如,要在 members
集合的 groupNumber
、lastname
和 firstname
字段上创建唯一索引,请在 mongosh
中使用以下操作:
db.members.createIndex( { groupNumber: 1, lastname: 1, firstname: 1 }, { unique: true } )
创建的索引强制 groupNumber
、lastname
和 firstname
值的组合具备唯一性。
以包含以下文档的集合为例:
{ _id: 1, a: [ { loc: "A", qty: 5 }, { qty: 10 } ] }
在 a.loc
和 a.qty
上创建唯一的复合多键索引:
db.collection.createIndex( { "a.loc": 1, "a.qty": 1 }, { unique: true } )
唯一索引允许将以下文档插入集合,因为该索引强制 a.loc
和 a.qty
值的组合有唯一性:
db.collection.insertMany( [ { _id: 2, a: [ { loc: "A" }, { qty: 5 } ] }, { _id: 3, a: [ { loc: "A", qty: 10 } ] } ] )
行为
限制
如果集合中已经包含会违反索引唯一性约束的数据,则 MongoDB 无法在指定的索引字段上创建唯一索引。
您不能在哈希索引上指定唯一约束。
在副本集和分片集群上构建唯一索引
对于副本集和分片集群,使用滚动过程创建唯一索引要求您在该过程中停止对集合的所有写入。如果在此过程中无法停止对集合的所有写入,请勿使用滚动过程。请通过以下方式在集合上构建唯一索引:
在副本集的主节点上发出
db.collection.createIndex()
,或者针对分片集群,在
mongos
上发出db.collection.createIndex()
。
跨单独文档的唯一性约束
唯一约束适用于集合中的独立文档。换言之,唯一索引可以防止独立文档为该索引键具有相同的值。
由于该约束适用于独立文档,因此对于唯一多键索引,只要某文档的索引键值与其他文档的索引键值不重复,该文档就可能包含导致重复索引键值的数组元素。在这种情况下,重复的索引条目仅插入索引一次。
例如,考虑包含以下文档的集合:
{ _id: 1, a: [ { loc: "A", qty: 5 }, { qty: 10 } ] } { _id: 2, a: [ { loc: "A" }, { qty: 5 } ] } { _id: 3, a: [ { loc: "A", qty: 10 } ] }
在 a.loc
和 a.qty
上创建唯一复合多键索引:
db.collection.createIndex( { "a.loc": 1, "a.qty": 1 }, { unique: true } )
如果此集合中没有其他文档的索引键值为 { "a.loc": "B", "a.qty": null }
,则该唯一索引允许将以下文档插入集合:
db.collection.insertOne( { _id: 4, a: [ { loc: "B" }, { loc: "B" } ] } )
唯一单字段索引中缺失文档字段
对于唯一单字段索引中的索引字段,如果某文档存在 null
或缺失值,则该索引为该文档存储 null
值。由于唯一性约束,单字段唯一索引只能包含一份文档,且该文档在其索引条目中包含 null
值。如果索引条目中存在多份具有 null
值的文档,则索引构建会失败并出现重复键错误。
例如,集合在 x
上具有唯一单字段索引:
db.collection.createIndex( { "x": 1 }, { unique: true } )
如果集合尚未包含缺少字段 x
的文档,则该唯一索引允许插入不带字段 x
的文档:
db.collection.insertOne( { y: 1 } )
如果集合已经包含一个缺失字段 x
的文档,则无法插入没有字段 x
的文档:
db.collection.insertOne( { z: 1 } )
由于违反对字段 x
的值的唯一性约束,因此该操作无法插入此文档:
WriteResult({ "nInserted" : 0, "writeError" : { "code" : 11000, "errmsg" : "E11000 duplicate key error index: test.collection.$a.b_1 dup key: { : null }" } })
唯一复合索引中缺失文档字段
如果某个文档的唯一复合索引中的一个或多个索引字段具有 null
或缺失值,则索引会为该文档索引条目中的每个 null
或缺失字段存储一个 null 值。由于唯一性约束,唯一复合索引仅允许一个文在索引条目中为所有索引字段具有 null
值。如果有多个索引条目针对所有索引字段的值均为 null
,则索引构建会失败并出现重复键错误。MongoDB 允许在唯一复合索引中存在缺少字段的多个文档,只要每个索引条目是唯一的。
例如,集合 students
在字段 name
、age
和 grade
上具有唯一复合索引:
db.students.createIndex( { "name": 1, "age": -1, "grade": 1 }, { unique: true } )
如果该集合不包含相同的文档,则该唯一复合索引允许插入以下全部缺少 grade
字段的文档。
db.students.insertMany( { "name": "Meredith", "age": 12 }, { "name": "Olivia", "age": 11 }, { "name": "Benjamin" } )
但是无法插入与集合中另一份文档具有相同索引键(name
、age
和 grade
的值)的文档。
db.students.insertOne( { name: "Meredith", age: 12 } )
该操作无法插入文档,因为它违反 name
、age
和 grade
字段值的唯一性约束:
WriteResult({ "nInserted" : 0, "writeError" : { "code" : 11000, "errmsg" : "E11000 duplicate key error collection: test.students index: name_1_age_-1_grade_1 dup key: { name: "Meredith", age: 12, grade: null } } } )
也无法插入唯一但与现有索引条目共享索引键的文档。
db.students.insertOne( { name: "Olivia", "age": 11, "favorite color": "red"} )
该操作无法插入文档,因为它违反 name
、age
和 grade
字段值的唯一性约束:
WriteResult({ "nInserted" : 0, "writeError" : { "code" : 11000, "errmsg" : "E11000 duplicate key error collection: test.students index: name_1_age_-1_grade_1 dup key: { name: "Olivia", age: 11, grade: null } } } )
唯一部分索引
部分索引仅对集合中满足指定过滤表达式的文档进行索引。如果同时指定 partialFilterExpression
和唯一性约束,则该唯一性约束仅适用于符合过滤器表达式的文档。
如果文档不满足过滤条件,具有唯一约束的部分索引则不会阻止插入不满足唯一约束的文档。有关示例,请参阅具有唯一约束的部分索引。
分片集群和唯一索引
不能在哈希索引上指定唯一约束。
对于范围分片集合,只有以下索引是唯一的:
重要
只有当 _id
字段也是分片键时,分片集群才会在整个集群中对 _id
字段执行唯一性约束。
如果 _id
字段不是分分片键或者只是分片分片键的前缀,则唯一性约束仅适用于分片文档的分片。这意味着两个或多个文档可以具有相同的 _id
值,前提是它们出现在不同的分片上。
示例,考虑一个分片分片键为 {x:
1}
的分分片的集合,它跨越两个分片 A 和 B。由于 _id
键不是分片分片键,因此该集合可能在分片 A 中包含 _id
值为 1
的文档以及分分片B 中另一个 _id
值为 1
的文档。
如果 _id
字段不是分片键,MongoDB 希望应用程序确保所有分片上 _id
值的唯一性。
唯一索引约束意味着:
对于要分片的集合,如果该集合有多个唯一索引,则无法分片集合进行分片,除非分分片键是所有唯一索引的前缀。
对于已分片的集合,除非包含分分片键作为前缀,否则无法在其他字段上创建唯一索引。
唯一索引会为缺少索引字段的文档存储空值;即缺失的索引字段被视为
null
索引键值的另一个实例。有关更多信息,请参阅唯一单字段索引中缺少文档字段。
要保持分片键字段的唯一性,请参阅任意字段的唯一性约束。
稀疏和非稀疏唯一索引
从 MongoDB 5.0 开始,具有相同键模式的唯一稀疏和唯一非稀疏索引可以存在于同一个集合中。
唯一和稀疏索引创建
此示例将使用相同的键模式和不同的 sparse
选项来创建多个索引:
db.scoreHistory.createIndex( { score : 1 }, { name: "unique_index", unique: true } ) db.scoreHistory.createIndex( { score : 1 }, { name: "unique_sparse_index", unique: true, sparse: true } )
基本索引和稀疏索引创建
还可以使用和不使用稀疏选项创建具有相同键模式的基本索引:
db.scoreHistory.createIndex( { score : 1 }, { name: "sparse_index", sparse: true } ) db.scoreHistory.createIndex( { score : 1 }, { name: "basic_index" } )
具有重复键模式的基本索引和唯一索引
从 MongoDB 5.0 开始,基本索引和唯一索引可以使用相同的键模式。
这种键模式的重复允许向已索引的字段添加唯一索引。
在本例中:
使用键模式 { score : 1 }
创建基本索引并插入三个文档。
db.scoreHistory.createIndex( { score : 1 }, { name: "basic_index" } ) db.scoreHistory.insert( { score : 1 } ) db.scoreHistory.insert( { score : 2 } ) db.scoreHistory.insert( { score : 3 } )
使用相同的键模式 { score : 1 }
创建唯一索引。
db.scoreHistory.createIndex( { score : 1 }, { name: "unique_index", unique: true } )
尝试插入重复的 score
文档,但由于该唯一索引而失败。
db.scoreHistory.insert( { score : 3 } )