在两个不同的集合中生成重复的 Mongo ObjectId 的可能性?
Posted
技术标签:
【中文标题】在两个不同的集合中生成重复的 Mongo ObjectId 的可能性?【英文标题】:Possibility of duplicate Mongo ObjectId's being generated in two different collections? 【发布时间】:2011-06-08 07:31:57 【问题描述】:是否可以为两个不同集合中的文档生成完全相同的 Mongo ObjectId?我意识到这绝对是不可能的,但有可能吗?
没有太具体,我问的原因是,通过我正在开发的应用程序,我们显示民选官员的公开资料,我们希望将他们转变为我们网站的正式用户。 We have separate collections for users and the elected officials who aren't currently members of our site. There are various other documents containing various pieces of data about the elected officials that all map back to the person using their elected official ObjectId.
After creating the account we still highlight the data that's associated to the elected official but they now also are a part of the users collection with a corresponding users ObjectId to map their profile to interactions with our application.
We had begun converting our application from mysql to Mongo a few months ago and while we're in transition we store the legacy MySql id for both of these data types and we're also starting to now store the elected official Mongo ObjectId在用户文档中映射回当所选的官方数据。
I was pondering just specifying the new user ObjectId as the previous elected official ObjectId to make things simpler but wanted to make sure that it wasn't possible to have a collision with any existing user ObjectId.
感谢您的洞察力。
编辑:发布此问题后不久,我意识到我提出的解决方案不是一个好主意。最好只保留我们现有的架构,并在用户文档中链接到选定的官方“_id”。
【问题讨论】:
见mongodb.org/display/DOCS/Object+IDs 我以前读过那个页面。具有讽刺意味的是,我实际上在之前的答案中链接到了同一页面。我确实看到了“相当高的独特性”免责声明,但不确定插入的收藏是否在其中发挥了任何作用。我想我不确定的是 ObjectId 的 2 字节进程 ID 部分到底代表了什么。如果它与集合有关,那么在不同集合中的完全相同的机器上同时创建的两个不同文档之间将存在唯一性。 2byte的进程id是生成ObjectID的进程的pid。例如,这里是 pymongo 用来生成 ObjectID 的代码:github.com/mongodb/mongo-python-driver/blob/master/bson/… 我遇到的一个问题是批量插入。我正在构建 10k 个文档的批次,并且每次都发生碰撞,因为计数器部分每次都滚动。 我知道已经有一段时间了,但是 10K 文档不会在柜台上翻滚。计数器部分是三个字节,而不是三个数字。超过 1600 万。 【参考方案1】:简答
直接回答您最初的问题:是的,如果您使用 BSON 对象 ID 生成,那么对于大多数驱动程序,ID 几乎肯定会在集合中是唯一的。请参阅下文了解“几乎可以肯定”的含义。
长答案
Mongo DB 驱动程序生成的 BSON 对象 ID 很可能在集合中是唯一的。这主要是因为 ID 的最后 3 个字节,对于大多数驱动程序是通过静态递增计数器生成的。该计数器是独立于收集的;它是全球性的。例如,Java 驱动程序使用随机初始化的静态 AtomicInteger。
那么,为什么在 Mongo 文档中,他们说 ID“很可能”是唯一的,而不是直接说它们将是唯一的?如果您无法获得唯一 ID,可能会出现三种可能性(如果还有更多,请告诉我):
在讨论之前,请记住 BSON 对象 ID 包括:
[4 字节秒数,3 字节机器哈希,2 字节进程 ID,3 字节计数器]
以下是三种可能性,您自己判断上当受骗的可能性有多大:
1) 计数器溢出:计数器有 3 个字节。如果您碰巧在一秒钟内插入超过 16,777,216 (2^24) 个文档,在同一台机器上,在同一进程中,那么您可能会溢出递增的计数器字节并最终得到两个共享同一时间的对象 ID,机器、进程和计数器值。
2) 计数器不递增:一些 Mongo 驱动程序使用随机数而不是递增计数器字节数。在这些情况下,有 1/16,777,216 的机会生成非唯一 ID,但前提是这两个 ID 在同一秒内(即在 ID 的时间段更新到下一秒之前),在同一时间机器,在同一个过程中。
3) 机器和进程散列到相同的值。在极不可能的情况下,机器 ID 和进程 ID 值可能会映射到两台不同机器的相同值。如果发生这种情况,并且同时两台不同机器上的两个计数器在同一秒内生成相同的值,那么您最终会得到一个重复的 ID。
这是需要注意的三种情况。场景 1 和 3 似乎不太可能,如果您使用正确的驱动程序,场景 2 是完全可以避免的。您必须检查驱动程序的来源才能确定。
【讨论】:
3字节计数器不是表示每台机器每进程每秒插入2^24 = 16777216个文档的能力吗? 你说得对,我不小心把位数减半了——答案已经被修改了。 由于我刚刚介入,让我补充一点,一些驱动程序(例如 C)虽然使用增量,但不会自动增量,因此有时会由于竞争条件而生成相同的 oid 你完全忽略了这样一个事实,即在 136 年后,只要机器哈希、进程 ID 和计数器全部显示为一样 @jamylak 我们会在紧急情况下处理这个问题(那些在 70 年代标准化 YYMMDD 日期格式的人说)【参考方案2】:ObjectIds 在客户端以类似于 UUID 的方式生成,但具有一些更好的存储在数据库中的属性,例如大致增加顺序和免费编码它们的创建时间。您的用例的关键在于,即使它们是在不同的机器上生成的,它们的设计也能保证高概率的唯一性。
现在,如果您通常指的是 _id 字段,我们不需要跨集合的唯一性,因此重用旧的 _id 是安全的。举个具体的例子,如果你有两个集合,colors
和fruits
,它们可以同时有一个像_id: 'orange'
这样的对象。
如果您想了解有关如何创建 ObjectId 的更多信息,请参阅以下规范:http://www.mongodb.org/display/DOCS/Object+IDs#ObjectIDs-BSONObjectIDSpecification
【讨论】:
【参考方案3】:如果有人遇到重复 Mongo ObjectID 的问题,您应该知道,尽管 Mongo 本身不太可能发生重复,但在 Mongo 中使用 php 生成重复的 _id 是可能的。
对我来说经常发生这种情况的用例是当我遍历数据集并尝试将数据注入集合时。
保存注入数据的数组必须在每次迭代时显式重置 - 即使您没有指定 _id 值。出于某种原因,INSERT 进程将 Mongo _id 添加到数组中,就好像它是一个全局变量一样(即使数组没有全局范围)。即使您在单独的函数调用中调用插入,这也会影响您,您通常希望数组的值不会持续回调用函数。
对此有三种解决方案:
-
你可以
unset()
数组中的_id字段
您可以在每次循环访问数据集时使用 array()
重新初始化整个数组
您可以自己显式定义 _id 值(注意以不自己生成副本的方式定义它)。
我的猜测是这是 PHP 界面中的一个错误,而不是 Mongo 的问题,但是如果您遇到这个问题,只需取消设置 _id 就可以了。
【讨论】:
见这里:php.net/manual/en/mongocollection.insert.php : "注意:如果参数没有 _id 键或属性,将创建一个新的 MongoId 实例并将其分配给它。这种特殊行为并不意味着参数是通过引用传递的。”,这是一个特性,而不是一个错误,它应该是这样的 我不明白你在这里描述的场景;或许您可以展示一些显示该错误的代码?【参考方案4】:无法保证 ObjectId 跨集合的唯一性。即使它在概率上非常不可能,也将是一个非常糟糕的应用程序设计,它依赖于 _id 跨集合的唯一性。
可以在 mongo shell 中轻松测试:
MongoDB shell version: 1.6.5
connecting to: test
> db.foo.insert(_id: 'abc')
> db.bar.insert(_id: 'abc')
> db.foo.find(_id: 'abc')
"_id" : "abc"
> db.bar.find(_id: 'abc')
"_id" : "abc"
> db.foo.insert(_id: 'abc', data:'xyz')
E11000 duplicate key error index: test.foo.$_id_ dup key: : "abc"
因此,绝对不要依赖 _id 在集合中的唯一性,并且由于您不控制 ObjectId 生成函数,因此不要依赖它。
可以创建更像 uuid 的东西,如果您手动执行此操作,则可以更好地保证唯一性。
请记住,您可以将不同“类型”的对象放在同一个集合中,那么为什么不将两个“表”放在同一个集合中。它们将共享相同的 _id 空间,因此可以保证是唯一的。从“预期”切换到“注册”将是一个简单的领域翻转......
【讨论】:
我认为您通常会将 _id 字段与 ObjectID 类型混淆。 ObjectID 类型是专为唯一性而设计的,其目标是可以将其视为 UUID。但是,_id 字段可以是任何类型,并且仅在您使用其他类型作为键(例如示例中的字符串)时才保证单个集合的唯一性。 @mstearn (Nitpick) UUID固有唯一的概念是有缺陷的。一个好的 UUID/序列生成策略可能会使冲突不太可能发生,但它需要考虑唯一生成器(例如唯一位置)以保证生成器之间的绝对唯一性。诚然,大多数概率很低,以至于没有适用的问题:-) GUID。 确实出现的一个问题是 id 的复制/复制而不是新一代。 @pst:MongoDB 的 ObjectID 包括生成进程的 pid 和一些基于主机名哈希的字节。这些与时间戳和递增计数器相结合,使得任何两个单独生成的 ObjectID 极有可能是全局/普遍唯一的。当然,正如你所说,这只适用于新生成的 ObjectID。 我指的是 ObjectId 类型。没有为“_id”指定字符串值。当然,如果您手动将它们设置为完全相同的字符串,它们将是相同的并且会发生冲突。 是的,我在帖子中澄清了一些事情。 _id 肯定不是唯一的,而且由于您不控制 ObjectId 生成函数,因此依赖它可能是个坏主意。以上是关于在两个不同的集合中生成重复的 Mongo ObjectId 的可能性?的主要内容,如果未能解决你的问题,请参考以下文章
mongo-> 如何从两个不同的集合中获取日期时间 desc 顺序的记录
使用 FluentAssertions 比较包含不同类型的两个字典集合