提问人:Christina 提问时间:3/6/2015 最后编辑:anothernodeChristina 更新时间:10/28/2022 访问量:100563
在 MongoDB 中使用 UUID 而不是 ObjectID
Using UUIDs instead of ObjectIDs in MongoDB
问:
出于性能原因,我们正在将数据库从 MySQL 迁移到 MongoDB,并考虑将什么用于 MongoDB 文档的 ID。我们正在争论使用ObjectIDs(MongoDB的默认值)还是使用UUID(这是我们迄今为止在MySQL中一直在使用的UUID)。到目前为止,我们必须支持这些选项中的任何一个的论据如下:
对象 ID:ObjectID 是 MongoDB 的默认值,我假设(尽管我不确定)这是有原因的,这意味着我希望 MongoDB 可以比 UUID 更有效地处理它们,或者有其他原因更喜欢它们。我还发现了这个 stackoverflow 答案,其中提到使用 ObjectID 可以提高索引效率,但是最好有一些指标来说明这种“效率”有多大。
UUID:我们支持使用 UUID 的基本论点(这是一个非常重要的论点)是,几乎任何数据库都以某种方式支持它们。这意味着,如果出于某种原因,我们决定从MongoDB切换到其他东西,并且我们已经有一个API,可以根据其ID从数据库中检索文档,那么对于该API的客户端来说,没有任何变化,因为ID可以继续完全相同。如果我们要使用 ObjectID,我真的不确定我们将如何将它们迁移到另一个数据库。
有没有人对这些选项中的一个是否可能比另一个更好以及为什么有任何见解?您是否曾经在 MongoDB 中使用过 UUID 而不是 ObjectID,如果是,您遇到的优点/问题是什么?
答:
MongoDB 的字段可以具有您想要的任何值,只要您能保证它对于集合是唯一的。当您的数据已经具有自然键时,没有理由不使用它来代替自动生成的 ObjectID。_id
ObjectID 是一种合理的默认解决方案,可以安全地生成自己的唯一键(并阻止初学者尝试复制 SQL,这在分布式数据库中是一个坏主意)。AUTO INCREMENT
如果不使用 ObjectID,您还会错过另一个便利功能:ObjectID 在生成时还包括一个 unix 时间戳,并且许多驱动程序提供了提取它并将其转换为日期的功能。这有时会使单独的字段变得多余。create-date
但是,当两者都不是您关心的问题时,您可以自由地使用您的 UUID 作为字段。_id
评论
考虑在每种情况下要存储的数据量。
MongoDB ObjectID 的大小为 12 字节,打包用于存储,其部分根据性能进行组织(即首先存储时间戳,这是一个逻辑排序标准)。
相反,标准 UUID 为 36 个字节,包含破折号,通常存储为字符串。此外,即使您剥离非数字字符并打算以数字方式存储,您仍然必须满足于其“索引”部分(UUID v1 中基于时间戳的部分)位于 UUID 的中间,并且不适合排序。有一些研究允许高性能的UUID存储,我甚至编写了一个Node.js库来协助其管理。
如果您打算使用 UUID,请考虑重新组织它以获得最佳索引和排序;否则,您可能会遇到性能障碍。
评论
0x04
前段时间,当我遇到同样的问题时,我发现了这些基准。 它们基本上表明,使用 Guid 而不是 ObjectId 会导致索引性能下降。
无论如何,我建议您自定义基准测试以模仿您的特定现实生活场景,并查看数字的样子,不能 100% 依赖通用基准测试。
在 Mongo 中使用 UUID 当然是可能的,并且得到了合理的支持。例如,Mongo 文档将 UUID 列为 _id
字段的常用选项之一。
考虑
- 性能 – 正如其他答案所提到的,基准测试显示 UUID 会导致插入的性能下降。在最坏的情况下(从集合中的 10M 到 20M 文档),它们的速度大约慢了 ~2-3 倍——这是每秒插入 2,000 个 (UUID) 和 7,500 个 (ObjectID) 文档之间的差异。这是一个很大的差异,但其重要性完全取决于您的用例。你会一次批量插入数百万个文档吗?对于我构建的大多数应用程序,常见的情况是插入单个文档。同样的基准表明,对于这种使用模式,差异要小得多(6,250 -vs- 7,500;~20%)。不容小觑。但也不是惊天动地。
- 可移植性 – 许多其他数据库平台都有很好的 UUID 支持,因此可移植性将得到提高。或者,由于 UUID 较大(更多位),因此可以将 ObjectID 重新打包为 UUID 的“形状”。这种方法不如直接可移植性好,但它确实为您提供了一种在现有 ObjectID 和 UUID 之间“映射”的方法。
- 去中心化 – UUID 的一大卖点是它们具有普遍的独特性。这使得以分散的方式在任何地方生成它们变得可行(例如,与自动递增值相反,自动递增值需要集中的事实来源来确定“下一个”值)。当然,Mongo 对象 ID 也承认了这种好处。不同之处在于,UUID 基于 15+ 年的标准,并且(几乎?)所有平台、语言等都受支持。如果您需要在不与数据库交互的情况下,它们将非常有用。您可以创建一个包含 ID 和外键的数据集,然后在将来的某个时间点将整个图形写入数据库,而不会发生冲突。尽管 Mongo ObjectID 也可以做到这一点,但找到生成它们/使用该格式的代码通常会更加困难。
修正
与其他一些答案相反:
- UUID 确实具有原生 Mongo 支持——您可以在 Mongo Shell 中使用
UUID()
函数,就像使用方式完全相同;将 UUID 字符串转换为等效的 BSON 对象。ObjectID()
- UUID 不是特别大 – 当使用二进制子类型编码时,它们是 128 位,而 ObjectID 为 96 位。(如果编码为字符串,它们将非常浪费,大约需要 288 位。
0x04
- UUID 可以包含时间戳 – 具体来说,UUIDv1 以 60 位的精度对时间戳进行编码,而 ObjectID 中为 32 位。在十进制中,这比精确度高出 6 个数量级以上——所以纳秒而不是秒。它实际上是一种不错的存储创建时间戳的方式,其准确性高于 Mongo/JS Date 对象支持,但是......
- 内置函数仅生成 v4(随机)UUID,因此,要利用这一点,您可以依靠您的应用程序或 Mongo 驱动程序来创建 ID。
UUID()
- 与 ObjectID 不同,由于 UUID 的分块方式,时间戳不会给出自然顺序。这可能是好事,也可能是坏事,具体取决于您的用例。(新标准可能会改变这一点;请参阅下面的 2021 年更新。
- 在您的 ID 中包含时间戳有时是个坏主意。您最终会在暴露 ID 的任何地方泄露文档的创建时间。(当然,ObjectID 也会对时间戳进行编码,因此对它们来说也部分正确。
- 如果使用(符合规范的)v1 UUID 执行此操作,则还会对服务器 MAC 地址的一部分进行编码,该地址可能用于标识计算机。对于大多数系统来说可能不是问题,但也不理想。(新标准可能会改变这一点;请参阅下面的 2021 年更新。
- 内置函数仅生成 v4(随机)UUID,因此,要利用这一点,您可以依靠您的应用程序或 Mongo 驱动程序来创建 ID。
结论
如果孤立地考虑 Mongo DB,则 ObjectID 是显而易见的选择。它们开箱即用,是一个完全有能力的默认值。相反,使用 UUID 确实会增加一些摩擦,无论是在处理值(需要转换为二进制类型等)时,还是在性能方面。这种轻微的不便是否值得拥有标准化的 ID 格式,实际上取决于您对可移植性和架构选择的重视程度。
您是否会在不同的数据库平台之间同步数据?将来会将数据迁移到其他平台吗?您是否需要在数据库之外、其他系统或浏览器中生成 ID?如果不是现在,在未来的某个时候?UUID 可能值得麻烦。
2021年8月更新
IEFT最近发布了UUID规范的更新草案,该草案将引入该格式的一些新版本。
具体来说,UUIDv6 和 UUIDv7 基于 UUIDv1,但翻转时间戳块,以便位从最重要到最不重要排列。这为结果值提供了一个自然顺序,该顺序(或多或少)反映了它们的创建顺序。新版本还排除了从服务器MAC地址派生的数据,解决了长期以来对v1 UUID的批评。
这些更改需要时间才能流向实现,但(恕我直言)它们显着现代化和改进了格式。
评论
uuid v4
更安全,MongoDB ID 是可预测的,并且容易受到记录枚举的影响。
我们必须小心区分MongoDB插入事物的成本与首先生成事物的成本以及相对于有效负载大小的成本。下面是一个小矩阵,它显示了根据可选的额外字节有效负载的大小生成交叉的方法。测试仅使用 javascript,在 MacBook Pro localhost 上进行 100,000 次插入,使用 100 个批次,没有事务,以尝试删除网络、聊天和其他因素。还进行了两次 batch = 1 的运行,只是为了突出戏剧性的差异。_id
insertMany
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%
这是一个快速的测试,但似乎很明显,基本字符串和 ints 的速度大致相同,但实际生成 UUID 会增加时间——特别是如果你采用对象的字符串版本,例如 还值得注意的是,构建一个似乎同样快。_id
UUID()
UUID().toString().substr(6,36)
ObjectId
在过去的几周里,我一直在思考这个问题。我只是发现 ObjectId 和 UUID 都是唯一的。事实上,在集合级别,您不能重复_id您使用的任何类型。一些答案谈到了插入性能。重要的是,这与插入性能无关,它需要的是索引性能。这需要根据您将用于索引_ids的内存量来计算。我们知道 ObjectId 是 12 个字节,而 UUID 是 36 个字节。它说,对于相同数量的索引,如果您使用 UUID 而不是 ObjectId,您将需要 2 倍的 ram 空间。
因此,从这个角度来看,最好在 mongodb 中使用 ObjectId 而不是 UUID。
UUID
是位(16 字节),并且是全局唯一的。请参阅 RFC 4122。128
Object Ids
是特定于 MongoDB 的构造,并且是位(12 字节)。尽管在全球范围内提供唯一性就足够了,但存在一些边缘条件。MongoDB有这个官方文档来比较两者。96
我们宁愿不被MongoDB特定的ID生成所束缚,而是更喜欢在客户端进行。我们还使用多种数据库。最重要的是,选择是人们可以根据其特定用例做出的决定。UUID
ObjectId
试试这个
const uuid = require('uuid')
const mongoose = require('mongoose')
const YourSchema = new Schema({
_id:{
type: String,
default: () => uuid.v4().replace(/\-/g, ""),
}
})
评论