ChatGPT解决这个技术问题 Extra ChatGPT

MySQL中的UUID性能?

我们正在考虑使用 UUID 值作为 MySQL 数据库的主键。插入的数据是从数十、数百甚至数千台远程计算机生成的,并且以每秒 100-40,000 次插入的速度插入,我们永远不会进行任何更新。

在我们开始剔除数据之前,数据库本身通常会达到大约 50M 条记录,因此不是一个庞大的数据库,但也不是很小的。我们还计划在 InnoDB 上运行,但如果有更好的引擎来支持我们正在做的事情,我们愿意改变这一点。

我们已经准备好使用 Java 的 Type 4 UUID,但在测试中看到了一些奇怪的行为。一方面,我们存储为 varchar(36),我现在意识到使用 binary(16) 会更好——尽管我不确定会更好。

更大的问题是:当我们有 50M 条记录时,这些随机数据对索引的影响有多大?例如,如果我们使用最左边的位带有时间戳的类型 1 UUID,我们会更好吗?或者也许我们应该完全放弃 UUID 并考虑 auto_increment 主键?

我正在寻找关于不同类型 UUID 在 MySQL 中作为索引/主键存储时的性能的一般想法/提示。谢谢!

缺少一个重要的细节:主键是由日志服务器生成还是由客户端机器自己生成?
@hop 它们是由插入数据的 10-1000 个客户端生成的
您在哪里需要场景中的普遍唯一性?我的建议是坚持使用 auto_increment 并使用单独的字段来描述发送数据的远程计算机。无需在这里重新发明轮子。
UUIDs 中有关性能缺陷的更多讨论

K
Kat Lim Ruiz

在我的工作中,我们使用 UUID 作为 PK。我可以从经验告诉你的是不要将它们用作 PK(顺便说一下 SQL Server)。

当您的记录少于 1000 条时,这是其中之一;没关系,但是当您拥有数百万条记录时,这是您能做的最糟糕的事情。为什么?因为 UUID 不是连续的,所以每次插入新记录时,MSSQL 都需要查看插入记录的正确页面,然后再插入记录。这样做的真正丑陋的后果是页面最终都以不同的大小结束并且它们最终碎片化,所以现在我们必须定期进行碎片整理。

当您使用自动增量时,MSSQL 将始终转到最后一页,并且您最终会得到相同大小的页面(理论上),因此选择这些记录的性能要好得多(也因为 INSERT 不会阻塞表/页面太长)。

但是,使用 UUID 作为 PK 的一大优势是,如果我们有 DB 集群,合并时不会发生冲突。

我会推荐以下模型: 1. PK INT Identity 2. 附加列自动生成为 UUID。

这样,合并过程是可能的(UUID 将是您的 REAL 密钥,而 PK 只是暂时的,可以为您提供良好的性能)。

注意:最好的解决方案是使用 NEWSEQUENTIALID(就像我在评论中所说的那样),但是对于没有太多时间重构的遗留应用程序(更糟糕的是,不控制所有插入),这是不可能的。但实际上截至 2017 年,我想说这里最好的解决方案是 NEWSEQUENTIALID 或使用 NHibernate 进行 Guid.Comb。

希望这可以帮助


我真的不知道这些术语是什么意思,但事实是每个月都需要重新索引索引。如果您提到的内容消除了重新索引任务,我不知道,但我可以问。
我一直在想的是,这对于亲子关系可能效果不佳。在这种情况下,我认为您必须在子表中添加:parent-pk,parent-guid。否则,您可能会丢失数据库之间的引用。我没有想太多,也没有做过任何例子,但这可能是需要的
@KatLimRuiz 在 sql server 你可以使用 NEWSEQUENTIALID() technet.microsoft.com/en-us/library/ms189786.aspx 来避免性能问题
确实,但 NEWSEQUENTIALID 只能作为 DEFAULT 使用。所以你需要围绕这个设计你的整个 DAL,这对于新项目来说是可以的,但对于大遗留项目来说并不那么容易
@KatLimRuiz 天才。这是一个很好的妥协
D
Dancrumb

UUID 是通用唯一 ID。这是您应该在这里考虑的普遍部分。

您真的需要 ID 是普遍唯一的吗?如果是这样,那么 UUID 可能是您唯一的选择。

我强烈建议如果您确实使用 UUID,请将它们存储为数字而不是字符串。如果您有 50M+ 记录,那么节省的存储空间将提高您的性能(虽然我不能说多少)。

如果您的 ID 不需要是普遍唯一的,那么我认为您不会比仅使用 auto_increment 做得更好,这可以保证 ID 在表中是唯一的(因为该值每次都会递增)


有趣的点;这将并行生成密钥。我相信这会提高密钥生成的性能。但是,如果您使用 VARCHAR 存储 UUID,则选择 INSERT 性能而不是 SELECT 性能。您绝对应该选择 VARBINARY 进行存储以确保 SELECT 性能。额外的步骤可能会影响 INSERT 性能,但您会因 SELECT 性能改进而获得回报。
我们最终对真实数据进行了一些基准测试,不带键的 GUID 非常快,带键的 GUID 非常糟糕(即使存储为 BINARY),而带 AUTO_COMPLETE 的 int 是最快的。我认为在我们的案例中,我们确实错过了树木中的森林,因为与存储更多数据的成本相比,序列生成似乎无关紧要 + 由于 GUID 的随机性,BTREE 非常糟糕
存储为数字意味着以二进制格式存储?但是二进制格式对人类来说是不可读的。它很慢,因为大字节的 uuid 主键?如果是,那么我可以将自动增量存储在 uuid 的另一列中。然后,性能不会受到影响。我对吗?
严格来说,UUID 是普遍唯一的,这意味着它永远不会出现在世界其他任何地方。仅当您公开共享数据时才需要此信息。至于将 UUID 存储为数字,我的意思不是 binary 格式。我的意思是 128 位数字,而不是 288 位字符串。例如,ASCII 中的单词“hello”是 68 65 6C 6C 6F,即数字 448,378,203,247。存储字符串 '68656C6C6F' 需要 10 个字节。数字 448,378,203,247 只需要 5。总而言之,除非你真的需要 UUID 中的第一个 U,否则你不能比 auto_increment 做得更好
@Chamnap:建议你问一个堆栈溢出问题:o)
K
Kyle Rosendo

需要考虑的一点是,自动增量是一次生成一个,并且不能使用并行解决方案来解决。使用 UUID 的斗争最终归结为您想要实现的目标与您可能牺牲的目标。

在性能方面,briefly

像上面这样的 UUID 长度为 36 个字符,包括破折号。如果您存储此 VARCHAR(36),您将显着降低比较性能。这是您的主键,您不希望它变慢。在位级别上,一个 UUID 是 128 位,这意味着它将适合 16 个字节,请注意这不是人类可读的,但它会保持较低的存储空间,并且仅比 32 位 int 大 4 倍,即 2比 64 位 int 大几倍。我将使用 VARBINARY(16) 从理论上讲,这可以在没有大量开销的情况下工作。

推荐阅读以下两篇文章:

Brian “Krow” Aker 的空闲想法 - 神话、GUID 与自动增量

到 UUID 还是不到 UUID ?

我认为两者之间,他们回答了你的问题。


实际上,我在发布这个问题之前阅读了这两篇文章,但我仍然没有一个好的答案。例如,两者都没有谈论 1 型和 4 型 UUIDS :(
公平地说,我稍微更新了我的答案。然而,我不认为它提供了太多额外的洞察力。
@Patrick:您在问题中提出了太多不同的主题。
9 年后,但也应该为后代注意的是,与整数 ID 不同,应用程序可以安全地生成 UUID,完全从数据库中删除生成。操作 UUID 以优化性能(基于时间戳,但经过修改以便可以天真地对它们进行排序)在 SQL 以外的几乎任何语言中都非常容易。幸运的是,今天几乎所有的数据库(包括 MySQL)都比以前更好地处理 UUID 主键。
G
Glenn J. Schworak

我倾向于避免使用 UUID,因为它存储起来很痛苦,用作主键也很痛苦,但也有一些优点。主要的是它们是独一无二的。

我通常通过使用双键字段来解决问题并避免使用 UUID。

收藏家 = 分配给机器的唯一

ID = 收集者收集的记录(auto_inc 字段)

这给了我两件事。 auto-inc 字段的速度和数据在收集和分组后存储在中心位置的唯一性。我在浏览收集数据时也知道,这通常对我的需求非常重要。

在为客户处理其他数据集时,我见过很多案例,他们决定使用 UUID,但仍然有一个字段用于收集数据,这确实是浪费精力。只需使用两个(或更多,如果需要)字段作为您的密钥真的很有帮助。

我刚刚看到太多使用 UUID 的性能损失。他们觉得自己像个骗子...


这实际上是一个非常简洁的方法,一旦被提及,听起来就很明显。问题是,当用于大连接或其他什么时,双键有多贵。
B
Bouke Versteegh

与其为每次插入集中生成唯一密钥,不如将密钥块分配给各个服务器?当他们用完密钥时,他们可以请求一个新的块。然后通过连接每个插入来解决开销问题。

Keyserver 维护下一个可用的 id

服务器 1 请求 id 块。

Keyserver 返回 (1,1000) 服务器 1 可以插入 1000 条记录,直到需要请求新块

服务器 2 请求索引块。

密钥服务器返回 (1001,2000)

ETC...

您可以提出一个更复杂的版本,其中服务器可以请求所需密钥的数量,或者将未使用的块返回给密钥服务器,这当然需要维护已使用/未使用块的映射。


理论上有趣的建议。这在实践中管理起来会很复杂。更实际的解决方案可能是 schworak 提出的答案。
t
theking2

我意识到这个问题相当古老,但我在研究中确实遇到了它。由于发生了很多事情(SSD 无处不在,InnoDB 得到了更新等)。

在我的研究中,我发现这个关于性能的post相当有趣:

声称由于 GUID/UUID 索引树的随机性,可能会变得相当不平衡。在 MariaDB KB 中,我发现 another post 提出了一个解决方案。但由于新的 UUID_TO_BIN 处理了这一点。此功能仅在 MySQL(测试版本 8.0.18)中可用,在 MariaDB(版本 10.4.10)中不可用

TL;DR:将 UUID 存储为转换/优化的 BINARY(16) 值。


N
Nikolai

我会以事务方式为每个服务器分配一个数字 ID。然后,插入的每条记录都会自动增加自己的计数器。 ServerID 和 RecordID 的组合将是唯一的。 ServerID 字段可以被索引,未来基于 ServerID(如果需要)的选择性能可能会更好。


S
StephenS

简短的回答是,由于索引方法与 UUID 在高位中的故意熵之间的冲突,许多数据库存在性能问题(特别是在插入量很大的情况下)。有几种常见的hack:

选择不介意的不同索引类型(例如 MSSQL 上的非聚集索引)

munge 数据以将熵移动到低位(例如,在 MySQL 上重新排序 V1 UUID 的字节)

使 UUID 成为具有自动增量 int 主键的辅助键

......但这些都是黑客 - 并且可能是脆弱的。

最好的答案,但不幸的是最慢的答案是要求您的供应商改进他们的产品,以便它可以像处理任何其他类型一样将 UUID 作为主键处理。他们不应该强迫您推出自己的半生不熟的黑客来弥补他们未能解决已成为常见用例并且只会继续增长的问题。


M
MindStalker

一些手工制作的 UID 呢?给数千台服务器中的每台服务器一个 ID,并让主键成为 autoincrement 的组合键,MachineID ???


我已经考虑过了,可能需要运行一些基准测试。即使是 1000 台机器中的每台机器上的临时本地序列,再加上时间戳,也可能就足够了。例如:machine_id + temp_seq + 时间戳
是否有可能有一个 temp_sequence 重置每个时间戳记?我不确定。
佚名

由于主键是分散生成的,因此您无论如何都无法选择使用 auto_increment。

如果您不必隐藏远程机器的身份,请使用 Type 1 UUID 而不是 UUID。它们更容易生成,至少不会损害数据库的性能。

varchar(char,真的)与二进制也是如此:它只能帮助事情。真的很重要吗,性能提高了多少?