MongoDB索引嵌入字段(点符号表示法)

7

假设这是代表客户的文档。

{
    company_name: 'corporate ltd.',
    pocs: [
       {name: 'Paul', email: 'paul@corporate.com'},
       {name: 'Jessica', email: 'jessica@corporate.com'}
    ]
}

我想为 pocs.email 定义一个唯一的索引, 因此我执行了以下命令:

db.things.ensureIndex({"pocs.email": 1}, {unique: true})

奇怪的是,当尝试添加一个拥有已经存在于另一家公司中的电子邮件的poc时,Mongo会拒绝该操作,遵循唯一索引约束。

也就是说,以下情况不允许存在:

{
    company_name: 'corporate ltd.',
    pocs: [
       {name: 'Paul', email: 'paul@corporate.com'},
       {name: 'Jessica', email: 'jessica@corporate.com'}
    ]
},
{
    company_name: 'contoso llc',
    pocs: [
       {name: 'Paul', email: 'paul@corporate.com'},
    ]
}

这很好。然而,在同一文档中存在重复的poc是可能的,例如:

{
    company_name: 'corporate ltd.',
    pocs: [
       {name: 'Paul', email: 'paul@corporate.com'},
       {name: 'Paul', email: 'paul@corporate.com'},
       {name: 'Jessica', email: 'jessica@corporate.com'}
    ]
},

以下是我使用的CLI命令序列:

> version()
version: 2.0.2
>
> use test
switched to db test
> db.test.ensureIndex({"poc.email": 1}, {unique: true})
> 
> db.test.insert({company: "contoso", poc: [{email: 'me@comapny.com'}]})
> db.test.insert({company: "contoso", poc: [{email: 'me@comapny.com'}]})
E11000 duplicate key error index: test.test.$poc.email_1  dup key: { : "me@comapny.com" }
> ({company: "contoso", poc: [{email: 'me.too@comapny.com'}, {email: 'me.too@company.com'}]})
> 
> 
> db.test.find()
{ "_id" : ObjectId("4f44949685926af0ecf9295d"), "company" : "contoso", "poc" : [ { "email" : "me@comapny.com" } ] }
{ "_id" : ObjectId("4f4494b885926af0ecf9295f"), "company" : "contoso", "poc" : [ { "email" : "me.too@comapny.com" }, { "email" : "me.too@company.com" } ] }

此外,这种情况会发生在插入更新时。
> db.test.update({"_id" : ObjectId("4f44949685926af0ecf9295d")}, {$push: { poc: {email: 'me@company.com'}}})
> db.test.find()
{ "_id" : ObjectId("4f4494b885926af0ecf9295f"), "company" : "contoso", "poc" : [ { "email" : "me.too@comapny.com" }, { "email" : "me.too@company.com" } ] }
{ "_id" : ObjectId("4f44949685926af0ecf9295d"), "company" : "contoso", "poc" : [        {       "email" : "me@comapny.com" },   {       "email" : "me@company.com" },   {       "email" : "me@company.com" } ] }
> 

这是一个bug还是我在文档中忽略的设计特性?

1个回答

8

有一个关于同样问题的未解决问题单个文档数组内的唯一索引不受执行的开放问题。您可以为其投票。

此外,Kyle Banker在这篇类似的帖子中建议了一个很好的解决方法嵌入式文档上的唯一索引

更新

这不仅与嵌入字段有关,我们也可以在数组字段中重现相同的问题。

>db.uniqqueTest.insert({a:[1],x:1})
>db.uniqqueTest.createIndex({a:1}, {unique: true})
> db.uniqqueTest.find()
{ "_id" : ObjectId("4f44c6252434860b44986b02"), "a" : [ 1 ],"x":1 }

如果我们尝试使用相同的值创建新文档,则会引发错误(正确的行为)。

> db.uniqqueTest.insert({a:[1],x:3})
E11000 duplicate key error index: stack.uniqqueTest.$a_1  dup key: { : 1.0 }

但是,如果我们在数组中放入相同的值(没有错误),这样也可以正常工作

> db.uniqqueTest.insert({a:[2],x:2})
> db.uniqqueTest.update({x:2},{$push:{a:2}})
{ "_id" : ObjectId("4f44c65f2434860b44986b05"), "a" : [ 2, 2 ], "x" : 2 }

但不是为了这个

> db.uniqqueTest.update({x:2},{$push:{a:1}])
E11000 duplicate key error index: stack.uniqqueTest.$a_1  dup key: { : 1.0 }

2
+1. 这被视为一个错误,但是一个相当奇怪的错误。 "唯一索引的设计目的是强制只有一个文档具有该键。从技术上讲,对于这种情况是正确的,但在大多数人使用该功能的方式中,他们希望唯一性包括子文档。" 我想知道他们是否能够修复它,因为这是一个相当重要的语义变化,可能会影响一些人。 - Thilo
@Thilo,我完全同意你的观点。如果他们接受了这一点,那将是一个巨大的变化,并且完全违背了唯一索引应有的本质。大多数人误解了嵌入/子文档的概念,认为它是一个真正的文档。这才是真正的问题所在。但是,“嵌入式文档只是另一个字段(具有嵌套属性),并且仅具有与普通字段相同的功能”。如果他们理解了这一点,这些问题就不会出现。 - RameshVel
@RameshVel 或许这只是一个观点问题,而不是[误]理解概念。 如果嵌套数组有N个项目,则现在索引表中有N个条目,每个项目一个。 不像“唯一索引的本质”为每个记录的索引定义创建单个条目。更重要的是,如果我们将子文档取出并放入单独的集合中,那么我们就回到了经典RDBMS所知道的连接地狱。 - Tzury Bar Yochay
@RameshVel:我认为将其视为错误是合理的。唯一应该意味着唯一,即使在同一文档中的多个键字段之间也是如此。但似乎以前没有人考虑过这个问题,当前行为已经存在,并且现在更改它可能比忍受它更麻烦。我们将看看这会变成什么样子。只需观察票证。至于嵌入式文档的讨论,我们是否知道这仅发生在嵌入式文档中?它不也发生在简单的顶级字段中吗?如果是这样,那么这与嵌入式文档无关。 - Thilo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接