我该在MongoDB中实现自增吗?

47
我正在从MySQL转换到MongoDB。对于一个非常基本的users表,我熟悉的架构是自增的uid请参阅Mongo的文档以了解此用例
我想知道这是否是最佳的架构决策。从用户体验的角度来看,我喜欢将UID作为外部引用,例如在较短的URL中:http://example.com/users/12345 有第三种方法吗?Freenode的IRC的#mongodb中有人建议创建一系列ID并缓存它们。我不确定如何实际实现它,或者是否有其他路线可走。我甚至不需要_id本身以这种方式递增。只要users文档中的所有用户都具有唯一的数字uid,我就会很高兴。
5个回答

95

我强烈不同意所选答案的作者关于“MongoDB 中没有自动增量 ID 并且有很好的原因”的说法。我们并不知道 10gen 为什么不鼓励使用自动增量 ID,这只是猜测。我认为 10gen 做出这个选择是因为在集群环境中确保 12 字节 ID 的唯一性更加容易。这是默认方案,适用于大多数新手,从而增加了产品采用率,对 10gen 的业务有利。

现在让我来分享一下我在商业环境中使用 ObjectIds 的经验。

我正在构建一个社交网络。我们大约有 6 百万用户,每个用户大约有 20 个好友。

现在想象一下,我们有一个集合,它存储用户之间的关系(谁关注谁)。它看起来像这样

_id : ObjectId
user_id : ObjectId
followee_id : ObjectId

我们拥有一个唯一的复合索引{user_id, followee_id}。我们可以估计该索引的大小为12*2*6M*20 = 2GB。这是用于快速查找我关注的人的索引。为了快速查找关注我的人,我需要反向索引。那又是另外2GB。

而这仅仅是个开始。我必须随身携带这些ID。我们有一个活动集群,我们在其中存储您的News Feed。那就是您或您的朋友所做的每个事件。想象一下它需要多少空间。

最后,我们的一位工程师做出了一个无意识的决定,并决定将参考文献存储为表示ObjectId的字符串,从而使其大小加倍。

如果一个索引无法适应RAM会发生什么?10gen说:什么好事都没有:

当索引太大无法适应RAM时,MongoDB必须从磁盘读取索引,这比从RAM读取要慢得多。请记住,当服务器具有可用于索引以及其余工作集合的RAM时,索引适合于RAM。

这意味着读取会变慢。锁竞争增加。写入也会变慢。看到80%左右的锁竞争对我来说已经不再震惊。

在你意识到之前,你就会得到一个460GB的集群,你必须将其分割成碎片,并且这很难操作。

Facebook使用64位长作为用户ID :) 这是有原因的。您可以生成顺序ID

  • 使用10gen的建议
  • 使用mysql作为计数器的存储(如果您关心速度,请参阅handlersocket
  • 使用您构建的ID生成服务或使用Twitter的Snowflake之类的东西。

所以这是我的一般建议。请尽可能使您的数据尽可能小。当您成长时,它将为您节省许多失眠的夜晚。


20
如果你有疑问,10gen是MongoDB的原始创造者,在2013年将他们的名字更改为MongoDB Inc.。 - Steve Eynon
虽然这与你的问题/抱怨/建议没有完全关系,但你可以使用以下代码: _id: {user_id, followee_id} 或者 {_id: user_id, followee_id: [array of <followee_id>] } - Sanfer

23

Josh,MongoDB中没有自动增量id,并且有很好的理由。我建议使用在集群中唯一的ObjectIds。

你可以通过序列集合添加自动增量,并使用findAndModify获取下一个要使用的id。这肯定会给你的应用程序增加复杂性,并可能影响数据库分片的能力。

只要你能保证生成的ids是唯一的,就不会有问题。但是头痛的问题将一直存在。

你可以查看此帖子以获取有关此问题的更多信息,该帖子位于MongoDB专用Google组中:

http://groups.google.com/group/mongodb-user/browse_thread/thread/f57b712b2aae6f0b/b4315285e689b9a7?lnk=gst&q=projapati#b4315285e689b9a7

希望这能帮到你。

谢谢


20

因此,“自动递增”ID存在一个根本性的问题。当你有10个不同的服务器(在MongoDB中称为分片)时,谁会选择下一个ID?

如果您想要单一集合的自动递增ID,则必须有单一的授权机构来选择这些ID。在MySQL中,这通常非常容易,因为您只需一个服务器接受写入即可。但是MongoDB的大规模部署正在运行分片,而这种方式没有这种“中央授权机构”。

MongoDB使用12字节的ObjectIds,因此每个服务器都可以创建唯一的新文档,而无需依赖单一授权机构。

所以这里有一个重要的问题:“你能承担得起一个单一的授权机构吗?”

如果可以,那么您可以使用findAndModify来跟踪“最后最高ID”,然后您可以插入该ID。

这就是您链接中描述的过程。这里明显的弱点是,从技术角度讲,每次插入您实际上需要进行两次写操作。这可能无法很好地扩展,您可能要避免对具有高插入率的数据进行操作。它可能适用于用户,但对于跟踪点击,则可能不起作用。


追踪点击是一个不好的示例,_id是否为ObjectId并不重要。你甚至可以使用复合_id来追踪点击。 - Karoly Horvath

12

在MongoDB中没有像自动递增这样的功能,但是您可以将自己的计数器存储在专用集合中,并根据需要$inc相关的计数器值。由于$inc是原子操作,所以不会出现重复。


也许我误解了你的意思,但我不确定这如何解决我的特定问题,因为您需要在每次插入时引用该计数器,并且由于插入不是原子操作,无法确保唯一性。 - Josh Smith
你可以在一个原子操作中获取一个新的ID,并在后续更新中使用它。这就是MongoDB给你的 - 没有多余的,也没有少的... - user2665694
1
我同意这个答案。即使插入操作不是原子性的,$inc 也是原子性的。我们可以使用 {new: true} 来获取计数器的新值。例如:counter.findByIdAndUpdate({_id: counterName}, {$inc: {seq: 1}}, {new: true}) - joeytwiddle

4

默认的Mongo ObjectId(用于_id字段)是递增的。

Mongo使用一个时间戳(自Unix纪元以来的秒数)作为其4-3-2-3组合中的前4个字节,非常类似(如果不完全相同)于版本1 UUID。如果用户/客户端没有提供其他类型的_id,则该ObjectId在插入时生成。

因此,ObjectId具有序号特性;此外,默认排序基于这个递增的时间戳。

可以认为它是许多DBMS中使用的自动递增(index++)ID的更新版本。


好的回答!完美! - Gabriel Simas
如何获取它? - MartianMartian

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