欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Mongodb 唯一部分索引

程序员文章站 2022-03-27 09:37:35
...

Mongodb抛出错误:E11000 duplicate key error index: test.collection.$a.b_1 dup key: { : null }

PS重点:mongodb 版本一定要在3.2以上才支持唯一部分索引

1 背景

最近有个需求,账户体系要同时支持手机号和邮箱。

使用mongodb存储数据时发现了一个问题:
我们肯定会要求邮箱和手机号在账号体系下均是唯一的。
即用上{ unique : true } 参数

但这就带来了一个问题,多个用户都用邮箱注册,第一个用户邮箱是可以注册成功的,因为email 唯一 且mobilenumber 缺失,mongodb会将该值存储为空值。但第二个用户注册就有抛出错误,因为email唯一,但mobilenumber同样缺失,同样是空值,这里就违反唯一索引的规则所以抛出了错误。

也就是mongodb 文档中提到了:唯一索引和缺失字段问题

2 唯一索引和缺失字段

Mongodb 遇到这种情况的行为是:
如果文档没有唯一索引中索引字段的值,则索引将为此文档存储空值。由于唯一约束,MongoDB只允许一个缺少索引字段的文档。如果有多个文档没有索引字段的值或缺少索引字段,则索引构建将失败并出现重复键错误。

即下面这种操作就会出现这种现象:

db.collection.createIndex( { "x": 1 }, { unique: true } )
db.collection.insert( { y: 1 } )
db.collection.insert( { z: 1 } )
WriteResult({
   "nInserted" : 0,
   "writeError" : {
      "code" : 11000,
      "errmsg" : "E11000 duplicate key error index: test.collection.$a.b_1 dup key: { : null }"
   }
})

3 解决既要保证某个key唯一又能允许缺失的方法

这里就可以用到:唯一部分索引

3.1 唯一部分索引概念

部分索引仅索引符合指定过滤器表达式的集合中的文档。如果同时指定 partialFilterExpression了唯一约束,
则唯一约束仅适用于满足过滤器表达式的文档。

如果文档不符合筛选条件,则具有唯一约束的部分索引不会阻止插入不符合唯一约束的文档。有关示例,请参阅 具有唯一约束的部分索引。

3.2 官方sample code

数据库中内容
{ "_id" : ObjectId("56424f1efa0358a27fa1f99a"), "username" : "david", "age" : 29 }
{ "_id" : ObjectId("56424f37fa0358a27fa1f99b"), "username" : "amanda", "age" : 35 }
{ "_id" : ObjectId("56424fe2fa0358a27fa1f99c"), "username" : "rajiv", "age" : 57 }
创建唯一部分索引 条件为age大于21
db.users.createIndex(
   { username: 1 },
   { unique: true, partialFilterExpression: { age: { $gte: 21 } } }
)
下列插入将被拒绝
db.users.insert( { username: "david", age: 27 } )
db.users.insert( { username: "amanda", age: 25 } )
db.users.insert( { username: "rajiv", age: 32 } )
下列插入将被允许
db.users.insert( { username: "david", age: 20 } )
db.users.insert( { username: "amanda" } )
db.users.insert( { username: "rajiv", age: null } )

3.3 实际使用

注意:一定要查看mongodb版本,mongod –version,大于3.2才支持。
为了解决moblienumber注册时,带来的email为空带来对唯一索引的问题。

按照官方文档,应该这样创建索引

db.users.createIndex(
   { email: 1 },
   { unique: true,  partialFilterExpression: { email: {$ne:null} } }
)

但实际使用中发现部分索引不支持$ne 表达式。
所以我们尝试$exists操作符。

所以对email字段不存在的情况下进行测试,发现下面两条语句效果一致:

db.users.find({email:{$eq:null}})
db.users.find({email:{$exists:false}})

所以我们创建唯一部分索引为

db.users.createIndex(
   { email: 1 },
   { unique: true,  partialFilterExpression: { email:  {$exists: true}} }
)

使用db.users.getIndexes()语句可以看到其中一个唯一部分索引为:

      {
                "v" : 2,
                "unique" : true,
                "key" : {
                        "mobilenumber" : 1
                },
                "name" : "mobilenumber_1",
                "ns" : "jwli.users",
                "partialFilterExpression" : {
                        "mobilenumber" : {
                                "$exists" : true
                        }
                },
                "background" : true
        }

4 参考链接

https://docs.mongodb.com/manual/core/index-unique/#unique-partial-indexes