【问题标题】:UUID performance in MySQL?MySQL中的UUID性能?
【发布时间】:2011-01-22 20:03:01
【问题描述】:

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

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

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

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

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

【问题讨论】:

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

标签: mysql performance innodb sequence uuid


【解决方案1】:

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

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

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

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

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

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

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

希望对你有帮助

【讨论】:

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

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:建议你问一个 Stack Overflow 问题:o)
【解决方案3】:

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

关于性能,briefly

像上面这样的 UUID 是 36 字符长,包括破折号。如果 你存储这个 VARCHAR(36),你是 会降低比较性能 戏剧性地。这是你的主要 关键,你不希望它变慢。

在位级别上,UUID 是 128 位, 这意味着它将适合 16 个字节, 请注意,这不是人类可读的, 但它会保持较低的存储空间,并且是 仅比 32 位 int 大 4 倍, 或比 64 位 int 大 2 倍。 我将使用 VARBINARY(16) 理论上,这可以在没有 很多开销。

我推荐阅读以下两篇文章:

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

【讨论】:

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

我倾向于避免使用 UUID,只是因为它很难存储并且用作主键很痛苦,但它也有优势。主要的是它们是独一无二的。

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

收集器 = 分配给机器的唯一对象

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

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

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

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

【讨论】:

    【解决方案5】:

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

    Keyserver 维护下一个可用的 id

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

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

    【讨论】:

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

    我意识到这个问题已经相当老了,但我确实在我的研究中遇到了这个问题。由于发生了很多事情(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) 值。

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

        • 选择不介意的其他索引类型(例如 MSSQL 上的非聚集索引)
        • 修改数据以将熵移动到低位(例如,在 MySQL 上重新排序 V1 UUID 的字节)
        • 使 UUID 成为具有自动增量 int 主键的辅助键

        ...但这些都是技巧——而且可能很脆弱。

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

        【讨论】:

          【解决方案9】:

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

          【讨论】:

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

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

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

          varchar(char,真的)与 binary 也是如此:它只能帮助解决问题。这真的很重要吗,性能提高了多少?

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-06-22
            • 1970-01-01
            • 1970-01-01
            • 2016-08-13
            • 1970-01-01
            • 2015-08-08
            相关资源
            最近更新 更多