在MongoDB中使用UUID代替ObjectID


77

出于性能原因,我们正在将数据库从MySQL迁移到MongoDB,并考虑将哪些内容用于MongoDB文档的ID。我们在使用ObjectID(这是MongoDB的默认值)还是使用UUID(这是我们到目前为止在MySQL中一直在使用的东西)之间进行辩论。到目前为止,我们必须支持以下任何一个选项的参数如下:

ObjectID: ObjectID是MongoDB的默认值,我假设(尽管不确定)这是有原因的,这意味着我希望MongoDB可以比UUID更有效地处理它们,或者有其他理由更喜欢它们。我还发现了这个stackoverflow答案,其中提到使用ObjectID可以使索引更有效,但是对一些“更有效”的大小有一些度量标准将是很好的。

UUID: 我们赞成使用UUID(这是非常重要的一个)的基本论据是,几乎任何数据库都以一种或另一种方式支持UUID。这意味着,如果将来由于某种原因我们决定从MongoDB切换到其他方式,并且我们已经有了一个API,该API可以根据数据库的ID从数据库中检索文档,因为该ID可以继续,因此该API的客户端没有任何变化完全一样。如果我们要使用ObjectID,我不太确定如何将它们迁移到另一个数据库。

是否有人对这些选择中的一种可能比另一种更好?为什么有任何见解?您是否曾经在MongoDB中使用UUID代替ObjectID,如果是,您遇到的优点/问题是什么?

Answers:


35

我认为这是个好主意,Mongo也是如此。他们将UUID列为_id字段的常见选项之一。

注意事项:

  • 性能-如其他答案所述,基准测试显示UUID导致插入的性能下降。在最坏的情况下(集合中的文档从1000万增加到2000万),它们的速度要慢大约2-3倍-每秒插入2,000(UUID)和7,500(ObjectID)文档之间的差异。这是一个很大的差异,但其重要性完全取决于您的用例。您会一次批量插入数百万个文档吗?对于大多数我构建的应用程序,常见的情况是插入单个文档。在那个测试中,差异小得多(6,250 -vs- 7,500;〜20%)。ID类型根本不是限制因素。
  • 可移植性-其他DB确实确实倾向于提供良好的UUID支持,因此可移植性将得到改善。或者,由于UUID较大(更多位),因此可以将ObjectID重新打包为UUID的“形状”。这种方法不如直接可移植性好,但确实为您提供了一条前进的道路。

与其他一些答案相反:

  • UUID具有本机支持-您可以完全按照使用方式在Mongo Shell中使用该UUID()功能ObjectID();将字符串转换为等效的BSON对象。
  • UUID并不是特别大-与96位的ObjectID相比,它们是128位。(它们应使用二进制子类型进行编码0x04。)
  • UUID可以包含时间戳记-具体地说,UUIDv1以60位精度对时间戳进行编码,而ObjectID中为32位。精度提高了6个数量级以上,因此是毫微秒而不是秒。实际上,这可能是一种比Mongo / JS Date对象更准确地存储创建时间戳的好方法,但是...
    • 内置UUID()函数仅生成v4(随机)UUID,因此,要利用此功能,您需要依靠应用或Mongo驱动程序来创建ID。
    • 与ObjectID不同,由于UUID的分块方式不同,因此时间戳记不会给您自然的顺序。这可能是好事,也可能是坏事,具体取决于您的用例。
    • 在您的ID中包含时间戳记通常不是一个好主意。您最终会泄漏任何暴露ID的文档的创建时间。更糟的是,v1 UUID还为在其上生成的计算机编码一个唯一标识符,该标识符可以公开有关您的基础结构的其他信息(例如服务器数量)。当然,ObjectID也会对时间戳进行编码,因此对它们来说也是如此。

46

_idMongoDB的字段可以具有您想要的任何值,只要可以保证它对于集合是唯一的即可。当您的数据已经具有自然键时,没有理由不使用它来代替自动生成的ObjectID。

提供ObjectID作为合理的默认解决方案,以确保安全时间生成自己的唯一密钥(并防止初学者尝试复制SQL AUTO INCREMENT ,这在分布式数据库中是个坏主意)。

通过不使用ObjectID,您还会错过另一个便利功能:ObjectID在生成时还包括Unix时间戳,许多驱动程序都提供了提取它并将其转换为日期的功能。有时这可能会使一个单独的create-date字段变得多余。

但是,当您不必担心这两者时,您可以自由地将UUID用作_id字段。


1
谢谢,事实是,我并不真正关心ID包含创建日期的信息(我已经将其作为单独的列)。您可能对两者之间的性能差异有任何见解吗?
克里斯蒂娜(Christina)2015年

9
您好克里斯蒂娜,实际上MongoDB Java驱动程序中有一张有趣的照片,其中显示了在ObjectId和UUID值jira.mongodb.org/browse/JAVA-403之间进行比较时的插入时间。着迷于最终了解您采用的方法。
Roman Blachman

1
UUIDv1还包含一个时间戳,精度提高了约6个数量级。与ObjectID的32位(秒)相比,UUIDv1编码了60位的时间(纳秒)。
Molomby

8

考虑在每种情况下要存储的数据量。

MongoDB ObjectID的大小为12个字节,打包存储以供存储,其组成部分按性能进行组织(即,首先存储时间戳,这是逻辑排序标准)。

相反,标准UUID为36字节,包含破折号,通常存储为字符串。此外,即使您剥离非数字字符并打算以数字方式存储,您仍然必须满足于其“索引”部分(基于时间戳的UUID v1部分)位于UUID的中间,并且不会可以很好地进行分类。已经进行了一些研究,这些研究可以实现高性能的UUID存储,我什至编写了一个Node.js库来协助对其进行管理。

如果您打算使用UUID,请考虑对其进行重组以实现最佳索引编制和排序。否则,您可能会遇到性能障碍。


可能会补充说,应该仔细考虑它,因为并非在所有情况下都无法分类/预测。例如,当生成会话ID时,您应该使用uuid v4版本(随机)。
罗宾F.18年

分片如何?您可以使用非散列的UUID进行分片,还是与ObjectID出现相同的问题,因为新写入将全部以一个分片结束?
mjaggard '18

1
没有理由将UUID存储为字符串...标准UUID恰好是16个字节,即使在mongo中也通常存储为原始字节。没有人使用v1 UUID,只有v4(随机)和v5(sha1)。
德米特里·古萨罗夫'18

3
如@Dmitry所述,UUID为16字节(128位),通常存储为字符串。MongoDB具有本机支持,并将其存储为Binary子类型0x04。虽然您对不幸的时间戳分块是正确的,但这确实是一个痛苦。我希望有一个更像SQUUID的正式UUID版本。
Molomby

1

当我有相同的问题时,我在某个时候找到了这些基准。它们基本上表明,使用Guid而不是ObjectId会导致索引性能下降。

无论如何,我还是建议您自定义基准,以模仿您的特定现实生活场景,并查看数字的样子,一个人不能100%依赖通用基准。


1

我们必须小心区分MongoDB插入事物的成本与首先生成事物的成本,再加上相对于有效负载大小的成本。下面是一个小的矩阵,显示了生成与_id可选的额外字节有效载荷大小相对的crossed方法。测试仅使用javascript,在MacBook Pro本地主机上进行了100,000次插入操作,使用insertMany了100个批次(无交易)以尝试消除网络,聊天和其他因素。也进行了两个批处理= 1的运行,只是为了突出显着差异。


Method                                                                                         
A  :  Simple int:          _id:0, _id:1, ...                                                   
B  :  ObjectId             _id:ObjectId("5e0e6a804888946fa61a1976"), ...                       
C  :  Simple string:       _id:"A0", _id:"A1", ...                                             

D  :  UUID length string   _id:"9575edcc-cb70-4d63-97ed-ee5d624de87b0", ...                    
      (but not actually                                                                        
      generated by UUID()                                                                      

E  :  Real generated UUID  _id: UUID("35992974-21ea-4f61-b715-2dfaed663b73"), ...              
      (stored UUID() object)                                                                   

F  :  Real generated UUID  _id: "6b16f733-ff24-4172-83f9-e4f96ace6775"                         
      (stored as string, e.g.                                                                  
      UUID().toString().substr(6,36)                                                           

Time in milliseconds to perform 100,000 inserts on fresh (empty) collection.

Extra                M E T H O D   (Batch = 100)                                                               
Payload   A     B     C     D     E     F       % drop A to F                                  
--------  ----  ----  ----  ----  ----  ----    ------------                                   
None      2379  2386  2418  2492  3472  4267    80%                                            
512       2934  2928  3048  3128  4151  4870    66%                                            
1024      3249  3309  3375  3390  4847  5237    61%                                            
2048      3953  3832  3987  4342  5448  5888    49% 
4096      6299  6343  6199  6449  7634  8640    37%                                            
8192      9716  9292  9397 10816 11212 11321    16% 

Extra              M E T H O D   (Batch = 1)                                          
Payload   A      B      C      D      E      F       % drop A to F              
--------  -----  -----  -----  -----  -----  -----                              
None      48006  48419  49136  48757  50649  51280   6.8%                       
1024      50986  50894  49383  49373  51200  51821   1.2%                       


这是一个快速的测试,但显然基本字符串和整数_id的速度大致相同,但是实际上生成UUID会增加时间-特别是如果您使用UUID()对象的字符串版本,例如UUID().toString().substr(6,36) ,还值得注意的是构造一个ObjectId外观尽快。

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.