Database 使用部分多键唯一索引时E11000(DuplicateKey)错误

Database 使用部分多键唯一索引时E11000(DuplicateKey)错误,database,mongodb,indexing,nosql,unique-constraint,Database,Mongodb,Indexing,Nosql,Unique Constraint,考虑包含以下文档的集合: { name: "John Doe", emails: [ { value: "some@domain.com", isValid: true, isPreferred: true } ] }, { name: "John Doe", emails: [ { value: "john.doe@

考虑包含以下文档的集合:

{ 
  name: "John Doe",
  emails: [
    { 
      value: "some@domain.com", 
      isValid: true, 
      isPreferred: true 
    }
  ]
},
{ 
  name: "John Doe",
  emails: [ 
    { 
       value: "john.doe@gmail.com",
       isValid: false,
       isPreferred: false
    },
    { 
       value: "john.doe@domain.com",
       isValid: true,
       isPreferred: true
    } 
  ]
}
应该没有用户拥有相同的有效和首选电子邮件,因此有一个唯一的索引:

db.users.createIndex( { "emails.value": 1 }, { name: "loginEmail", unique: true, partialFilterExpression: { "emails.isValid": true, "emails.isPreferred": true } } )
将以下电子邮件添加到第一个文档会触发唯一约束冲突:

{
  name: "John Doe",
  emails: [
   { 
     value: "john.doe@gmail.com",
     isValid: false,
     isPreferred: false
   }
  ]
}
原因:com.mongodb.MongoCommandException:命令失败 错误11000(重复密钥):“E11000重复密钥错误集合: profiles.users索引:loginEmail dup键:{emails.value: “约翰。doe@gmail.com,emails.isValid:false,emails.isPreferred:false }'on server profiles db mongodb.dev:27017。全部答复是: {“ok”:0.0,“errmsg”:“E11000重复密钥错误集合: profiles.users索引:loginEmail dup键:{emails.value: “约翰。doe@gmail.com,emails.isValid:false,emails.isPreferred: false},“code”:11000,“codeName”:“DuplicateKey”,“keyPattern”: {“emails.value”:1,“emails.isValid”:1,“emails.isPreferred”:1}, “keyValue”:{“emails.value”:“john。doe@gmail.com“,“emails.isValid”: false,“emails.isPreferred”:false}

正如我所理解的,之所以会发生这种情况,是因为过滤器表达式应用于集合,而不是嵌入文档,因此尽管有点违反直觉和出乎意料,但索引的行为如所述

我的问题是如何确保部分唯一性而不出现假阴性?

TLDR:你不能

让我们先了解为什么会发生这样的事情,也许然后我们就会明白我们能做些什么。问题源于两个Mongo特性的组合

  • 语法。点表示法语法允许您轻松地查询数组中的子文档(
    “emails.isPreferred”:true
    )。但是,当您想开始使用子文档时,例如在您的案例中,您需要使用一些类似的内容,例如,的限制非常严格,并且没有给您这样的权限。 这意味着即使是带有电子邮件的文档,例如:
  • 将被索引。所以,好的,我们将在集合中有一些额外的索引文档,但除了(错误地)增加索引大小之外,您仍然希望它可能会起作用,但由于第2点的原因,它不会起作用

  • :
  • MongoDB使用多键索引来索引存储在数组中的内容MongoDB为数组的每个元素创建单独的索引项。

    因此,当您在数组或数组中子文档的任何字段上创建索引时,Mongo将“展平”数组,并为每个文档创建唯一的条目。在这种情况下,它将为数组中的所有
    电子邮件创建唯一索引

    因此,由于所有这些“特性”和部分过滤器语法使用的限制,我们无法真正实现您想要的

    那你能做什么呢?我相信你已经在考虑解决这个问题的办法了。一个简单的解决方案是维护一个额外的字段,该字段只包含那些
    isValid
    isPreferred
    电子邮件。然后,一个唯一的稀疏索引就可以做到这一点

    {
        "_id": ObjectId("5f106c0e823eea49427eea64"),
        "name": "John Doe",
        "emails": [
            {
                "value": "john.doe@gmail.com",
                "isValid": true,
                "isPreferred": false
            },
            {
                "value": "john.doe@domain.com",
                "isValid": false,
                "isPreferred": true
            }
        ]
    }