ChatGPT解决这个技术问题 Extra ChatGPT

在两个不同的集合中生成重复的 Mongo ObjectId 的可能性?

是否可以为两个不同集合中的文档生成相同的 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 in the users document to map back to the elected official data.

我正在考虑只是将新用户 ObjectId 指定为先前选出的官方 ObjectId 以使事情变得更简单,但想确保不可能与任何现有用户 ObjectId 发生冲突。

感谢您的洞察力。

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

我以前读过那个页面。具有讽刺意味的是,我实际上在之前的答案中链接到了同一页面。我确实看到了“相当高的独特性”免责声明,但不确定插入的收藏是否在其中发挥了任何作用。我想我不确定的是 ObjectId 的 2 字节进程 ID 部分到底代表了什么。如果它与集合有关,那么在不同集合中的完全相同的机器上同时创建的两个不同文档之间将存在唯一性。
2byte 进程 id 是生成 ObjectID 的进程的 pid。例如,以下是 pymongo 用于生成 ObjectID 的代码:github.com/mongodb/mongo-python-driver/blob/master/bson/…
我遇到的一个问题是批量插入。我正在构建 10k 个文档的批次,并且每次都发生碰撞,因为计数器部分每次都翻转。
我知道已经有一段时间了,但是 10K 文件不会在柜台上翻滚。计数器部分是三个字节,而不是三个数字。超过1600万。

R
Raj Advani

简答

只是直接回答您最初的问题:是的,如果您使用 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 和计数器全部相同,您就可以再次生成与以前相同的 ObjectId
@jamylak 当问题变得紧急时,我们会处理这个问题(那些在 70 年代标准化 YYMMDD 日期格式的人说)
m
mstearn

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

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

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


D
DenverMatt

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

对我来说,这种情况经常发生的用例是当我循环数据集并尝试将数据注入集合时。

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

对此有三种解决方案:

您可以 unset() 数组中的 _id 字段您可以在每次遍历数据集时使用 array() 重新初始化整个数组您可以自己显式定义 _id 值(注意以您不这样做的方式定义它) t 自己生成副本)。

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


见这里:php.net/manual/en/mongocollection.insert.php : "注意:如果参数没有 _id 键或属性,则会创建一个新的 MongoId 实例并分配给它。这种特殊行为并不意味着参数是通过引用传递的。",这是一个功能,而不是一个错误,它应该是那样的
我不明白你在这里描述的场景;也许您可以显示一些显示错误的代码?
s
slacy

无法保证 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 生成函数,因此依赖它可能是个坏主意。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅