是否可以在两个不同的集合中生成重复的Mongo ObjectId?


187

是否可以为两个不同集合中的文档生成完全相同的Mongo ObjectId?我意识到这绝对不可能,但是有可能吗?

无需太具体,我想问的原因是我正在开发的应用程序向我们展示了当选官员的公开资料,他们希望将其转换为我们网站的正式用户。我们为用户和当前不是我们网站成员的民选官员提供单独的收藏集。还有其他各种文档,其中包含有关民选官员的各种数据,所有数据都使用其民选官员ObjectId映射回该人。

创建帐户后,我们仍将突出显示与当选官员相关的数据,但它们现在也是用户集合的一部分,带有相应的用户ObjectId,以将其个人资料映射到与我们的应用程序的交互。

几个月前,我们已经开始将应用程序从MySql转换为Mongo,而在过渡期间,我们为这两种数据类型存储了旧版MySql ID,现在我们也开始在用户中存储当选的官方Mongo ObjectId。文档以映射回民选的官方数据。

我正在考虑仅将新用户ObjectId指定为先前的当选官方ObjectId,以简化操作,但想确保与任何现有用户ObjectId都不冲突。

感谢您的见解。

编辑:发布此问题后不久,我意识到我提出的解决方案不是一个好主意。最好保留我们现有的当前模式,并仅链接到用户文档中当选的官方“ _id”。



1
我之前已经读过该页面。具有讽刺意味的是,我实际上在上一个答案中链接到了同一页面。我确实看到了“独特的可能性很高”的免责声明,但不确定将插入的收藏夹是否发挥了任何作用。我想我不确定ObjectId的2字节Process ID部分究竟代表什么。如果与集合有关,那么在完全相同的时间在不同集合中的完全相同的机器上创建的两个不同文档之间将存在唯一性。
安东尼·杰克

1
2byte进程ID是生成ObjectID的进程的pid。作为一个例子,这里是代码pymongo用来生成的ObjectID:github.com/mongodb/mongo-python-driver/blob/master/bson/...
mstearn

我遇到的一个陷阱是批量插入。我正在构建成批的1万个文档,并且每次都会碰撞,因为计数器部分每次都会翻转。
fawce 2011年

我知道已经有一段时间了,但是1万个文档不会在柜台上滚动。计数器部分是三个字节,而不是三个数字。超过1600万。
Asya Kamsky 2014年

Answers:


318

简短答案

只是为了直接回答您的第一个问题:是的,如果您使用BSON对象ID生成,那么对于大多数驱动程序而言,ID几乎可以肯定在集合中是唯一的。有关“几乎可以肯定”的含义,请参见下文。

长答案

Mongo DB驱动程序生成的BSON对象ID在各个集合中极有可能是唯一的。这主要是由于ID的最后3个字节,对于大多数驱动程序而言,它是通过静态递增计数器生成的。该计数器是独立于集合的;这是全球性的。例如,Java驱动程序使用随机初始化的静态AtomicInteger。

那么,为什么在Mongo文档中他们说ID“很有可能”是唯一的,而不是直截了当地说它们将是唯一的?当您无法获得唯一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插入的文档数吗?
Forrest Ye'2

您是完全正确的,我不小心将位数减半了-答案已被修改。
拉杰·阿德瓦尼

由于我只是进入了这一步,所以我要补充一点,尽管某些驱动程序(例如C)使用增量,但不会原子地递增,因此由于种族状况,它有时会生成相同的oid
Pawel Veselov 2012年

39
您完全跳过了以下事实:在136年中ObjectId,只要机器哈希,进程ID和计数器都相同,就可以再次生成与以前相同的镜头
jamylak 2014年

25
@jamylak我们将在紧急情况下解决这个问题(那些在70年代标准化YYMMDD日期格式的人说)
Philipp

14

ObjectId以类似于UUID的方式在客户端生成,但具有一些更好的属性可以存储在数据库中,例如,粗略增加顺序并免费编码其创建时间。您的用例的关键是,即使它们是在不同的机器上生成的,它们也被设计为保证唯一性的可能性很高。

现在,如果您通常指的是_id字段,则我们不需要跨集合的唯一性,因此可以安全地重用旧的_id。举一个具体的例子,如果您有两个集合colorsfruits,那么两个都可以同时具有像这样的对象{_id: 'orange'}

如果您想了解有关如何创建ObjectId的更多信息,请参见以下规范:http : //www.mongodb.org/display/DOCS/Object+IDs#ObjectIDs-BSONObjectIDSpecification


11

万一有人在使用重复的Mongo ObjectID时遇到问题,您应该知道,尽管Mongo本身不太可能发生重复,但在Mongo中可能会用PHP生成重复的_id。

对于我来说,定期发生这种情况的用例是当我遍历数据集并尝试将数据注入到集合中时。

即使每次未指定_id值,都必须在每次迭代时显式重置保存注入数据的数组。由于某种原因,INSERT进程会将Mongo _id添加到数组中,就好像它是一个全局变量一样(即使该数组没有全局作用域)。即使您在单独的函数调用中调用插入操作,这也会影响您,在通常情况下,您通常希望数组的值不会持久化回调用函数。

有三种解决方案:

  1. 您可以unset()从数组的_id字段
  2. 您可以在array()每次遍历数据集时重新初始化整个数组。
  3. 您可以自己明确定义_id值(请谨慎定义它,以免自己产生重复)。

我的猜测是,这是PHP界面中的错误,而与Mongo无关,而只是一个问题,但是如果遇到此问题,只需取消设置_id,就可以了。


参见此处:php.net/manual/en/mongocollection.insert.php:“注意:如果参数没有_id键或属性,则会创建一个新的MongoId实例并将其分配给它。这种特殊行为并不意味着该参数是通过引用传递的。”,这是一个功能,而不是错误,它就是那样的方式
Oliver Konig 2014年

1
我不明白您在此描述的情况;也许您可以显示一些显示该bug的代码?
Mark Amery

-7

不能保证集合之间的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空间,因此将被保证是唯一的。从“预期”切换到“注册”将是对字段的简单翻转...


1
我认为您通常会将_id字段与ObjectID类型混淆。ObjectID类型是专门为唯一性而设计的,目的是可以将其视为UUID。但是,_id字段可以是任何类型,并且仅当您对键使用其他类型(例如示例中的字符串)时,才保证单个集合的唯一性。
mstearn

@mstearn(Nitpick)UUID 本质上是唯一的概念存在缺陷。良好的UUID /序列生成策略可能使冲突不太可能发生,但是它需要考虑唯一的生成器(例如,唯一的位置),以确保生成器之间的绝对唯一性。当然,大多数都具有如此低的概率,因此没有适用的问题:-) GUID。但是,确实出现了一个问题,就是ID的复制/复制而不是新一代的复制/复制。

1
@pst:MongoDB的ObjectID包括生成过程的pid和基于主机名哈希的一些字节。这些与时间戳和递增计数器结合在一起,使得任何两个单独生成的ObjectID极有可能在全局/全局上都是唯一的。当然,正如您所说,这仅适用于新生成的ObjectID。
mstearn 2011年

1
我指的是ObjectId类型。没有为“ _id”指定字符串值。当然,如果您将它们手动设置为完全相同的字符串,它们将是相同的并且会冲突。
安东尼·杰克

是的,我澄清了帖子中的内容。_id当然不是唯一的,并且由于您不控制ObjectId生成函数,因此依赖它可能是一个坏主意。
slacy 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.