我们正在考虑使用 UUID 值作为 MySQL 数据库的主键。插入的数据是从数十、数百甚至数千台远程计算机生成的,并且以每秒 100-40,000 次插入的速度插入,我们永远不会进行任何更新。
在我们开始剔除数据之前,数据库本身通常会达到大约 50M 条记录,因此不是一个庞大的数据库,但也不是很小的。我们还计划在 InnoDB 上运行,但如果有更好的引擎来支持我们正在做的事情,我们愿意改变这一点。
我们已经准备好使用 Java 的 Type 4 UUID,但在测试中看到了一些奇怪的行为。一方面,我们存储为 varchar(36),我现在意识到使用 binary(16) 会更好——尽管我不确定会更好。
更大的问题是:当我们有 50M 条记录时,这些随机数据对索引的影响有多大?例如,如果我们使用最左边的位带有时间戳的类型 1 UUID,我们会更好吗?或者也许我们应该完全放弃 UUID 并考虑 auto_increment 主键?
我正在寻找关于不同类型 UUID 在 MySQL 中作为索引/主键存储时的性能的一般想法/提示。谢谢!
在我的工作中,我们使用 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。
希望这可以帮助
UUID 是通用唯一 ID。这是您应该在这里考虑的普遍部分。
您真的需要 ID 是普遍唯一的吗?如果是这样,那么 UUID 可能是您唯一的选择。
我强烈建议如果您确实使用 UUID,请将它们存储为数字而不是字符串。如果您有 50M+ 记录,那么节省的存储空间将提高您的性能(虽然我不能说多少)。
如果您的 ID 不需要是普遍唯一的,那么我认为您不会比仅使用 auto_increment 做得更好,这可以保证 ID 在表中是唯一的(因为该值每次都会递增)
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
做得更好
需要考虑的一点是,自动增量是一次生成一个,并且不能使用并行解决方案来解决。使用 UUID 的斗争最终归结为您想要实现的目标与您可能牺牲的目标。
在性能方面,briefly:
像上面这样的 UUID 长度为 36 个字符,包括破折号。如果您存储此 VARCHAR(36),您将显着降低比较性能。这是您的主键,您不希望它变慢。在位级别上,一个 UUID 是 128 位,这意味着它将适合 16 个字节,请注意这不是人类可读的,但它会保持较低的存储空间,并且仅比 32 位 int 大 4 倍,即 2比 64 位 int 大几倍。我将使用 VARBINARY(16) 从理论上讲,这可以在没有大量开销的情况下工作。
推荐阅读以下两篇文章:
Brian “Krow” Aker 的空闲想法 - 神话、GUID 与自动增量
到 UUID 还是不到 UUID ?
我认为两者之间,他们回答了你的问题。
我倾向于避免使用 UUID,因为它存储起来很痛苦,用作主键也很痛苦,但也有一些优点。主要的是它们是独一无二的。
我通常通过使用双键字段来解决问题并避免使用 UUID。
收藏家 = 分配给机器的唯一
ID = 收集者收集的记录(auto_inc 字段)
这给了我两件事。 auto-inc 字段的速度和数据在收集和分组后存储在中心位置的唯一性。我在浏览收集数据时也知道,这通常对我的需求非常重要。
在为客户处理其他数据集时,我见过很多案例,他们决定使用 UUID,但仍然有一个字段用于收集数据,这确实是浪费精力。只需使用两个(或更多,如果需要)字段作为您的密钥真的很有帮助。
我刚刚看到太多使用 UUID 的性能损失。他们觉得自己像个骗子...
与其为每次插入集中生成唯一密钥,不如将密钥块分配给各个服务器?当他们用完密钥时,他们可以请求一个新的块。然后通过连接每个插入来解决开销问题。
Keyserver 维护下一个可用的 id
服务器 1 请求 id 块。
Keyserver 返回 (1,1000) 服务器 1 可以插入 1000 条记录,直到需要请求新块
服务器 2 请求索引块。
密钥服务器返回 (1001,2000)
ETC...
您可以提出一个更复杂的版本,其中服务器可以请求所需密钥的数量,或者将未使用的块返回给密钥服务器,这当然需要维护已使用/未使用块的映射。
我意识到这个问题相当古老,但我在研究中确实遇到了它。由于发生了很多事情(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) 值。
我会以事务方式为每个服务器分配一个数字 ID。然后,插入的每条记录都会自动增加自己的计数器。 ServerID 和 RecordID 的组合将是唯一的。 ServerID 字段可以被索引,未来基于 ServerID(如果需要)的选择性能可能会更好。
简短的回答是,由于索引方法与 UUID 在高位中的故意熵之间的冲突,许多数据库存在性能问题(特别是在插入量很大的情况下)。有几种常见的hack:
选择不介意的不同索引类型(例如 MSSQL 上的非聚集索引)
munge 数据以将熵移动到低位(例如,在 MySQL 上重新排序 V1 UUID 的字节)
使 UUID 成为具有自动增量 int 主键的辅助键
......但这些都是黑客 - 并且可能是脆弱的。
最好的答案,但不幸的是最慢的答案是要求您的供应商改进他们的产品,以便它可以像处理任何其他类型一样将 UUID 作为主键处理。他们不应该强迫您推出自己的半生不熟的黑客来弥补他们未能解决已成为常见用例并且只会继续增长的问题。
一些手工制作的 UID 呢?给数千台服务器中的每台服务器一个 ID,并让主键成为 autoincrement 的组合键,MachineID ???
由于主键是分散生成的,因此您无论如何都无法选择使用 auto_increment。
如果您不必隐藏远程机器的身份,请使用 Type 1 UUID 而不是 UUID。它们更容易生成,至少不会损害数据库的性能。
varchar(char,真的)与二进制也是如此:它只能帮助事情。真的很重要吗,性能提高了多少?