在 MongoDB 中使用 UUID 而不是 ObjectID

出于性能考虑,我们正在将数据库从 MySQL 迁移到 MongoDB,并考虑如何使用 MongoDB 文档的 ID。我们正在讨论是使用 ObjectID (MongoDB 默认值) ,还是使用 UUID (这是我们到目前为止在 MySQL 中一直使用的方法)。到目前为止,我们支持这些选择的理由如下:

目标 ID: ObjectIDs are the MongoDB default and I assume (although I'm not sure) that this is for a reason, meaning that I expect that MongoDB can handle them more efficiently than UUIDs or has another reason for preferring them. I also found 这个堆栈溢出的答案 that mentions that usage of ObjectIDs makes indexing more efficient, it would be nice however to have some metrics on how much this "more efficient" is.

UUID: 我们支持使用 UUID 的基本论点(这是一个非常重要的论点)是,它们受到几乎任何数据库的支持。这意味着,如果我们决定从 MongoDB 切换到其他服务,而且我们已经有了一个基于 ID 从 DB 检索文档的 API,那么这个 API 的客户端不会有任何变化,因为 ID 可以保持完全相同。如果我们要使用 ObjectID,我不确定我们将如何将它们迁移到另一个数据库。

是否有人对这些选择中的一个是否比另一个更好,以及为什么有任何见解?您曾经在 MongoDB 中使用过 UUID 而不是 ObjectID 吗? 如果有,您遇到的优点/问题是什么?

79238 次浏览

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

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

由于不使用 ObjectID,您还错过了另一个方便的特性: ObjectID 在生成时还包含一个 unix 时间戳,许多驱动程序提供了一个函数来提取它并将它转换为日期。这有时会使单独的 create-date字段变得多余。

但是如果两者都不需要考虑,那么您可以自由地将 UUID 用作 _id字段。

Consider the amount of data you would store in each case.

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

相反,标准 UUID 为36字节,包含破折号,通常以字符串形式存储。此外,即使您去掉非数字字符并打算以数字形式存储,您仍然必须满足其“ indexy”部分(基于时间戳的 UUID v1的部分)位于 UUID 的中间,并且不适合进行排序。有 研究的做法,允许性能 UUID 存储,我甚至写了一个 Node.js 库,以协助其管理。

If you intend to use a UUID, consider reorganizing it for optimal indexing and sorting; otherwise you'll likely hit a performance wall.

我发现这些 基准的一段时间前,当我有同样的问题。 它们基本上表明,使用 Guid 而不是 ObjectId 会导致索引性能下降。

无论如何,我建议您定制基准来模仿您特定的现实生活场景,并看看数字是什么样子的,一个人不能100% 依赖于一般的基准。

在 Mongo 中使用 UUID 当然是可能的,并且得到了相当好的支持。例如,Mongo 文档将 UUID 列为 _id的常见选项之一。

考虑因素

  • Performance -正如其他答案所提到的,benchmarks显示 UUID 会导致插入的性能下降。在最坏的情况下(从一个集合中的10M 到20M 文档) ,它们的速度要慢大约2-3倍——即每秒插入2000个(UUID)和7500个(ObjectID)文档之间的差异。这是一个很大的区别,但它的重要性完全取决于您的用例。你会一次批量地插入数百万份文件吗?对于大多数我构建的应用程序来说,通常的情况是插入单独的文档。相同的基准测试表明,对于该使用模式,差异较小(6,250-vs-7,500; ~ 20%)。不是无关紧要。.但也不是惊天动地。
  • 可移植性 -许多其他 DB 平台有良好的 UUID 支持,因此可移植性将得到改善。另外,由于 UUID 更大(更多位) ,repack an ObjectID into the "shape" of a UUID也是可能的。这种方法不像直接可移植性那样好,但它确实为您提供了一种在现有 ObjectID 和 UUID 之间“映射”的方法。
  • 地方分权 -UUID 最大的卖点之一就是它们都是独一无二的。这使得在任何地方以分散的方式生成它们变得切实可行(例如,与自动递增价值相反,自动递增价值需要一个集中的真理来源来确定“下一个”价值)。当然,Mongo 对象 ID 也承认了这一点。区别在于,UUID 基于15年以上的旧标准,并且支持(几乎?)所有平台、语言等。如果您需要在不连接的系统中创建实体(或者具体地说,创建一组 相关的实体) ,而不需要与数据库交互,那么它们就非常有用。您可以创建一个带有 ID 和外键的数据集,然后在将来的某个时候将整个图形写入数据库,而不会发生冲突。虽然 Mongo ObjectID 也可以做到这一点,但是找到生成它们的代码/使用这种格式通常会更加困难。

更正

与其他一些答案相反:

  • UUID 确实有原生的 Mongo 支持 -您可以在 Mongo Shell 中使用 UUID() function,就像您使用 ObjectID(); 到 将 UUID 字符串转换为等效的 BSON 对象一样。
  • UUID 并不特别大 -当使用二进制子类型 0x04编码时,它们是128位,而 ObjectID 是96位。(如果将它们编码为字符串,那么它们的 abc 1就相当浪费了,因为它们占用了288比特。)
  • UUID 可以包含时间戳 -具体来说,UUIDv1以60位的精度编码时间戳,而 ObjectID 为32位。在十进制中,这个数量级的精度提高了6倍以上——所以是 纳秒而不是秒。它实际上是一种比 Mongo/JS Date 对象支持的更精确的存储创建时间戳的体面方法,但是..。
    • UUID()函数中的构建只生成 v4(随机) UUID,所以要利用这一点,您需要依靠应用程序或 Mongo 驱动程序来创建 ID。
    • Unlike ObjectIDs, because of UUID 分块的方式, the timestamp doesn't give you a natural order. This can be good or bad depending on your use case. (New standards may change this; see 2021 update below.)
    • 在 ID 中包含时间戳有时是一个糟糕的主意。您最终会在 ID 暴露的任何地方泄漏文档的创建时间。(当然 ObjectID 也编码一个时间戳,因此对它们来说也是部分正确的。)
    • If you do this with (spec compliant) v1 UUIDs, you're also encoding part of the servers MAC address, which can potentially be used to identify the machine. Probably not an issue for most systems but also not ideal. (New standards may change this; see 2021 update below.)

结论

如果您单独考虑 MongoDB,ObjectID 是显而易见的选择。他们工作出色的盒子,是一个完美的能力默认。使用 UUID 代替 是的会增加一些摩擦,无论是在处理值时(需要转换为二进制类型等)还是在性能方面。这种轻微的不便是否值得拥有标准化的 ID 格式,实际上取决于您对可移植性和架构选择的重视程度。

您将在不同的数据库平台之间同步数据吗?将来您会将数据迁移到不同的平台吗?是否需要在其他系统或浏览器中生成数据库的 ID 在外面?如果不是现在,在未来的某个时刻?UUID 可能是值得的麻烦。

2021年8月最新情况

IEFT 最近发布了 UUID 规范的更新草案,将引入该格式的一些新版本。

具体来说,UUIDv6UUIDv7基于 UUIDv1,但是要翻转时间戳块,以便将位从最高有效位排列到最低有效位。这使得结果值具有自然的顺序,(或多或少)反映了它们被创建的顺序。新版本还排除了来自服务器 MAC 地址的数据,解决了长期以来对 v1 UUID 的批评。

这些变更流向实现需要时间,但是(恕我直言)它们显著地实现了格式的现代化和改进。

We must be careful to distinguish the cost of MongoDB inserting a thing vs. the cost to generate the thing in the first place 还有 that cost relative to the size of the payload. Below is a little matrix that shows method of generating the _id crossed against the size of an optional extra bytes worth of payload. Tests are using javascript only, conducted on MacBook Pro localhost for 100,000 inserts using insertMany of batches of 100 without transactions to try to remove network, chatty, and other factors. Two runs with batch = 1 were also done just to highlight the dramatic difference.


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是大致相同的速度,但实际上 generating一个 UUID 增加时间-特别是如果你采取的字符串版本的 UUID()对象,例如 UUID().toString().substr(6,36)值得注意的是,构造一个 ObjectId似乎是快速的。

过去几周我一直在思考这个问题。我只是发现 ObjectId 和 UUID 都是唯一的。实际上,在集合级别中,不管使用什么类型,都不能使用 copy _ id。有些答案是关于插入性能的。重要的不是插入性能,而是索引性能。这需要根据您将使用多少内存来计算 indexing _ ids。我们知道 ObjectId 是12字节,其中 UUID 是36字节。它说,对于相同数量的索引,如果使用 UUID 而不是 ObjectId,则需要多2倍的内存空间。

因此,从这个角度来看,最好在 mongodb 中使用 ObjectId over UUID。

UUID128位(16字节)并且是全局唯一的。参见 RFC 4122

Object Ids are a MongoDB specific construct and is 96 bits (12 bytes). And although it would be enough to provide uniqueness globally but there are some edge conditions. MongoDB has this official document to compare the two.

我们不希望与 MongoDB 特定的 ID 生成绑定在一起,而是希望在客户端进行这项工作。我们还使用多种类型的数据库。底线是,选择 UUID而不是 ObjectId是一个人们可以根据他们的具体用例做出的决定。

试试这个

    const uuid = require('uuid')
const mongoose = require('mongoose')
const YourSchema = new Schema({
_id:{
type: String,
default: () => uuid.v4().replace(/\-/g, ""),
}




})