本文将会介绍 MongoDB 唯一索引,它可以用于确保文档字段值的唯一性。
唯一索引
很多时候我们需要确保文档中某个字段值的唯一性,例如 email 或者 username。唯一索引(unique index)可以帮助我们实现这种业务规则。实际上,MongoDB 使用唯一索引确保主键 _id 的唯一性。
创建唯一索引的方法和普通索引相同,只需要额外指定 {unique: true} 选项:
索引示例
首先,创建一个新的集合 users,插入一些文档:
db.users.insertMany([
{ name: "张三", dob: "1990-01-01", email: "[email protected]"},
{ name: "李四", dob: "1992-06-30", email: "[email protected]"}
]);
然后,我们基于 email 字段创建一个唯一索引:
接下来我们尝试插入一个已经存在的文档:
db.users.insertOne(
{ name: "张叁", dob: "1995-03-12", email: "[email protected]"}
);
此时,MongoDB 将会返回以下错误:
下面我们演示一下集合已经存在重复数据时如何创建唯一索引。首先删除并重建集合 users:
db.users.drop()
db.users.insertMany([
{ name: "张三", dob: "1990-01-01", email: "[email protected]"},
{ name: "张叁", dob: "1995-03-12", email: "[email protected]"},
{ name: "李四", dob: "1992-06-30", email: "[email protected]"}
]);
然后,基于 email 字段创建一个唯一索引:
db.users.createIndex({email: 1},{unique:true})
MongoServerError: Index build failed: 95f78956-d5d0-4882-bfe0-2d856df18c61: Collection book.users ( 6da472db-2884-4608-98b6-95a003b4f29c ) :: caused by :: E11000 duplicate key error collection: mflix.users index: email_1 dup key: { email: zhangsan@test.com }
以上错误的原因在于 email 中存在重复的记录。
通常,我们会在插入数据之前创建唯一索引,可以从头开始确保数据的唯一性。如果基于已有数据创建唯一索引,可能会由于重复数据导致索引创建失败。为此,我们需要删除重复的数据之后再创建索引。
例如,我们可以首先删除重复的 user:
db.users.deleteOne({ name: "张叁", dob: "1995-03-12", email: "[email protected]"});
{ acknowledged: true, deletedCount: 1 }
然后基于 email 字段创建唯一索引:
db.users.createIndex({email: 1},{unique:true})
email_1
复合唯一索引
基于多个字段创建的唯一索引就是复合唯一索引(unique compound index)。复合唯一索引可以确保多个字段值的组合唯一。例如,基于字段 field1 和 field2 创建复合唯一索引,以下数据具有唯一性:
field1 | field2 | 组合 |
---|---|---|
1 | 1 | (1,1) |
1 | 2 | (1,2) |
2 | 1 | (2,1) |
2 | 2 | (2,2) |
以下数据存在重复值:
field1 | field2 | 组合 |
---|---|---|
1 | 1 | (1,1) |
1 | 1 | (1,1) 重复 |
2 | 1 | (2,1) |
2 | 1 | (2,1) 重复 |
接下来我们看一个示例,首先创建一个集合 locations。
db.locations.insertOne({
address: "北京市丰台区莲花池东路118号北京西站",
latitude: 39.894793,
longitude: 116.321592
})
然后,基于 latitude 和 longitude 创建一个复合唯一索引:
db.locations.createIndex({
latitude: 1,
longitude: 1
},{ unique: true });
'latitude_1_longitude_1'
插入一个经度相同的地点:
db.locations.insertOne({
address: "北京市海淀区复兴路甲9号中华世纪坛",
lat: 39.910577,
long: 116.321592
})
操作执行成功,因为复合索引 latitude_1_longitude_1 校验的是经度和纬度的组合值。
最后,插入一个经纬度已经存在的地点:
db.locations.insertOne({
address: "北京市丰台区莲花池东路118号北京西站",
latitude: 39.894793,
longitude: 116.321592
})
此时,MongoDB 返回重复键错误: