使用部分多键唯一索引时出现 E11000 (DuplicateKey) 错误
E11000 (DuplicateKey) error when using a partial multikey unique index
考虑包含以下文档的集合:
{
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
}
]
}
Caused by: com.mongodb.MongoCommandException: Command failed with
error 11000 (DuplicateKey): 'E11000 duplicate key error collection:
profiles.users index: loginEmail dup key: { emails.value:
"john.doe@gmail.com", emails.isValid: false, emails.isPreferred: false
}' on server profiles-db-mongodb.dev:27017. The full response is
{"ok": 0.0, "errmsg": "E11000 duplicate key error collection:
profiles.users index: loginEmail dup key: { emails.value:
"john.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 特征的组合。
- dot notation syntax. The dot notation syntax allows you to query subdocuments in arrays at ease (
"emails.isPreferred": true
). However when you want to start using multiple conditions for subdocuments like in your case you need to use something like $elemMatch sadly the restrictions for partialFilterExpression 非常严格,不会给你这样的权力。
这意味着即使是带有电子邮件的文档,例如:
{
"_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
}
]
}
将被编入索引。好吧,我们将在集合中添加一些额外的索引文档,但除了(错误地)增加索引大小之外,您仍然希望它可能起作用,但由于第 2 点,它不是。
MongoDB uses multikey indexes to index the content stored in arrays. ... , MongoDB creates separate index entries for every element of the array.
因此,当您在数组或数组中子文档的任何字段上创建索引时,Mongo 将“展平”数组并为每个文档创建一个唯一的条目。在这种情况下,它将为数组中的所有 emails
创建一个唯一索引。
所以由于所有这些“功能”和部分过滤器语法使用的限制,我们无法真正实现您想要的。
那你能做什么?我相信您已经在考虑可能的解决方法。一个简单的解决方案是维护一个仅包含 isValid
和 isPreferred
电子邮件的额外字段。那么唯一的稀疏索引就可以解决问题。
考虑包含以下文档的集合:
{
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
}
]
}
Caused by: com.mongodb.MongoCommandException: Command failed with error 11000 (DuplicateKey): 'E11000 duplicate key error collection: profiles.users index: loginEmail dup key: { emails.value: "john.doe@gmail.com", emails.isValid: false, emails.isPreferred: false }' on server profiles-db-mongodb.dev:27017. The full response is {"ok": 0.0, "errmsg": "E11000 duplicate key error collection: profiles.users index: loginEmail dup key: { emails.value: "john.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 特征的组合。
- dot notation syntax. The dot notation syntax allows you to query subdocuments in arrays at ease (
"emails.isPreferred": true
). However when you want to start using multiple conditions for subdocuments like in your case you need to use something like $elemMatch sadly the restrictions for partialFilterExpression 非常严格,不会给你这样的权力。 这意味着即使是带有电子邮件的文档,例如:
{
"_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
}
]
}
将被编入索引。好吧,我们将在集合中添加一些额外的索引文档,但除了(错误地)增加索引大小之外,您仍然希望它可能起作用,但由于第 2 点,它不是。
MongoDB uses multikey indexes to index the content stored in arrays. ... , MongoDB creates separate index entries for every element of the array.
因此,当您在数组或数组中子文档的任何字段上创建索引时,Mongo 将“展平”数组并为每个文档创建一个唯一的条目。在这种情况下,它将为数组中的所有 emails
创建一个唯一索引。
所以由于所有这些“功能”和部分过滤器语法使用的限制,我们无法真正实现您想要的。
那你能做什么?我相信您已经在考虑可能的解决方法。一个简单的解决方案是维护一个仅包含 isValid
和 isPreferred
电子邮件的额外字段。那么唯一的稀疏索引就可以解决问题。