MySQL中的UUID性能?


82

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

在我们开始选择数据之前,数据库本身通常将获得约5000万条记录,因此不是庞大的数据库,也不是很小的数据库。我们也计划在InnoDB上运行,但是如果我们有更好的引擎来进行我们的工作,我们愿意改变它。

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

更大的问题是:当我们拥有5000万条记录时,此随机数据对索引的破坏有多严重?如果我们使用例如类型1的UUID标记最左边的比特,我们会更好吗?还是我们应该完全放弃UUID并考虑使用auto_increment主键?

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


2
缺少一个重要的细节:是由日志服务器还是由客户端计算机本身生成主键?

1
@hop它们是由插入数据的10-1000个客户端生成的
Patrick Lightbody,2010年

您的方案在哪里需要通用唯一性?我的建议是坚持使用auto_increment并使用一个单独的字段来描述发送数据的远程计算机。无需在这里重新发明轮子。
Theodore Zographos

Answers:


35

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

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

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

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


2
有趣的一点;这将并行化密钥的生成。我相信这将提高密钥生成的性能。但是,如果使用VARCHAR存储UUID,则选择INSERT性能而不是SELECT性能。您绝对应该选择VARBINARY进行存储,以确保SELECT性能。额外的步骤可能会影响INSERT性能,但是SELECT性能的提高将使您受益匪浅。
Dancrumb

12
我们最终对真实数据进行了一些基准测试,没有键的GUID非常快,带有键的GUID太可怕了(即使存储为BINARY),并且带有AUTO_COMPLETE的int是最快的。我认为在我们的案例中,我们确实从树上错失了森林,因为与GUID的随机性相比,存储更多数据+拥有非常糟糕的BTREE的成本相比,序列生成似乎无关紧要
Patrick Lightbody 2010年

1
以数字形式存储意味着以二进制格式存储?但是二进制格式对于人类来说是不可读的。因为uuid主键的字节大,所以速度很慢?如果是这样,那么我可以将自动增量存储在uuid的另一列中。这样,性能就不会受到影响。我对吗?
Chamnap 2012年

4
严格来说,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
Dancrumb 2012年

1
@Chamnap:建议您问一个堆栈溢出问题:o)
Dancrumb 2012年

77

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

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

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

但是,将UUID用作PK的最大好处是,如果我们有数据库集群,则合并时不会有冲突。

我建议使用以下模型:1. PK INT身份标识2.其他列自动生成为UUID。

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

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

希望这可以帮助


我真的不知道这些术语的含义,但事实是索引需要每月重新索引一次。如果您提到的消除了重新编制索引的任务,我不知道,但是我可以问。
Kat Lim Ruiz 2012年

3
我一直在想的是,这可能不适用于亲子关系。在这种情况下,我认为您必须在子表中添加:parent-pk,parent-guid。否则,您可能会丢失数据库之间的引用。我没有考虑太多,也没有做任何示例,但这可能是需要的
Kat Lim Ruiz 2013年

4
您可以在SQL Server中使用@KatLimRuiz使用NEWSEQUENTIALID()technet.microsoft.com/zh-cn/library/ms189786.aspx来避免性能问题
giammin 2013年

确实,但是NEWSEQUENTIALID仅用作DEFAULT。因此,您需要围绕此设计整个DAL,这对于新项目是可行的,但对于大型遗产而言却不那么容易
Kat Lim Ruiz

@KatLimRuiz天才。这是一个很大的折衷
jmgunn87 2014年

26

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

关于性能,简要地

上面的UUID长度为36个字符,包括破折号。如果存储此VARCHAR(36),将大大降低比较性能。这是您的主键,您不希望它变慢。

在其位级别上,UUID是128位,这意味着它可以容纳16个字节,请注意,这不是很容易理解,但是会保持较低的存储量,并且仅是32位int的4倍,即2比64位int大10倍。从理论上讲,我将使用VARBINARY(16),它可以正常工作而没有太多开销。

我建议阅读以下两篇文章:

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


2
实际上,在发布此问题之前,我已经阅读了这两篇文章,并且在这里我仍然没有很好的答案。例如,没有人谈论1型和4型UUIDS :(
Patrick Lightbody,2010年

公平地说,我更新了我的答案。我认为它并没有提供太多额外的见识。
凯尔·罗森多

@帕特里克:您在问题中输入了太多不同的主题。

1
9年后,但也应注意,与整数ID不同的是,应用程序可以安全地生成UUID,从而完全从数据库中删除了生成的UUID。使用UUID进行性能优化(基于时间戳但经过修改,以便可以对其进行幼稚的排序),几乎可以使用除SQL之外的任何其他语言进行操作。幸运的是,今天几乎所有数据库(包括MySQL)对UUID主键的处理都比以前好得多。
Miles Elam

5

我倾向于避免UUID仅仅是因为它很难存储并且很难用作主键,但是有很多优点。主要的是它们是唯一的。

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

COLLECTOR =唯一分配给机器

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

这给了我两件事。收集并分组在一起后,自动增量字段的速度和存储在中央位置的数据的唯一性。我还知道在浏览数据收集位置时,这对于我的需求通常非常重要。

我已经看到许多情况,在为客户决定使用UUID的客户处理其他数据集时,他们仍然有一个字段来收集数据,这确实是在浪费精力。简单地使用两个(或更多,如果需要)字段作为您的密钥确实有帮助。

我刚刚发现使用UUID会带来太多性能下降。他们觉得自己像个骗子...


3

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

密钥服务器维护下一个可用的ID

  • 服务器1请求ID块。
  • 密钥服务器返回(1,1000)
    服务器1可以插入1000条记录,直到需要请求新块为止
  • 服务器2请求索引块。
  • 密钥服务器返回(1001,2000)
  • 等等...

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


理论上有趣的建议。在实践中,这将很复杂。schworak可能会给出一个更实际的解决方案。
西蒙·伊斯特

2

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


2

简短的答案是,由于许多数据库的索引编制方法与UUID在高阶位中的故意熵之间存在冲突,因此它们存在性能问题(尤其是INSERT量大)。有几种常见的技巧:

  • 选择一个不介意的索引类型(例如,非聚类的MSSQL)
  • 调整数据以将熵移至低位(例如,在MySQL上重新排列V1 UUID的字节)
  • 使用自动递增的int主键将UUID用作辅助键

...但是这些都是骇客-可能还很脆弱。

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


1

手工制作的UID呢?给数千个服务器中的每一个提供一个ID,并使主键成为自动递增的组合键,MachineID。


我已经考虑过了,可能需要运行一些基准测试。即使在1000台计算机中的每台计算机上使用临时本地序列,再加上时间戳,也足够了。例如:machine_id + temp_seq +时间戳
Patrick Lightbody,2010年

是否有一个temp_sequence可以在每个时间戳记滴答时重置?我不确定。
MindStalker 2010年

1

由于主键是分散生成的,因此您始终无法选择使用auto_increment。

如果不必隐藏远程计算机的标识,请使用Type 1 UUID代替UUID。它们更易于生成,并且至少不会损害数据库的性能。

varchar(char,实际上)与二进制文件相同:它只能帮助解决问题。真正重要的是,性能提高了多少?

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.