【问题标题】:MySQL PRIMARY KEYs: UUID / GUID vs BIGINT (timestamp+random)MySQL主键:UUID / GUID vs BIGINT(时间戳+随机)
【发布时间】:2011-09-14 09:38:03
【问题描述】:

tl;dr:如果我不想处理 UUID,那么将 {unixtimestamp}{randomdigits}(例如 1308022796123456)的行 ID 分配为 BIGINT 是个好主意吗?

只是想知道是否有人对分配给跨多个服务器的数据库记录的 ID/PRIMARY KEY 的任何性能或其他技术考虑/限制有所了解。

我的 PHP+MySQL 应用程序运行在多台服务器上,并且需要能够合并数据。所以我已经超出了识别行的标准顺序/自动增量整数方法。

我对解决方案的研究使我想到了使用 UUID/GUID 的概念。然而,需要更改我的代码以处理在 MySQL 中将 UUID 字符串转换为二进制值似乎有点痛苦/工作。出于存储和性能原因,我不想将 UUID 存储为 VARCHAR。

存储在二进制列中的 UUID 的另一个可能的烦恼是,在 PhpMyAdmin 中查看数据时行 ID 并不明显——尽管我可能错了——但总体而言,直数看起来要简单得多,而且在任何类型的数据库系统中通用,无需转换。

作为中间立场,我想出了将我的 ID 列设为 BIGINT 的想法,并使用当前的 unix 时间戳后跟 6 个随机数字来分配 ID。所以假设我的随机数大约是 123456,我今天生成的 ID 会是:1308022796123456

在同一秒内创建的行发生冲突的可能性为 1000 万分之一,这对我来说很好。我不会快速进行任何类型的大规模行创建。

我读到的关于随机生成的 UUID 的一个问题是它们不利于索引,因为值不是连续的(它们散布在各处)。 MySQL 中的 UUID() 函数通过从当前时间戳生成 UUID 的第一部分来解决这个问题。因此,我复制了在我的 BIGINT 开头使用 unix 时间戳的想法。我的索引会变慢吗?

我的 BIGINT 想法的优点:

  • 为我提供了 UUID 的多服务器/合并优势
  • 只需要对我的应用程序代码进行很少的更改(一切都已编程为处理 ID 的整数)
  • UUID 存储空间的一半(8 字节 vs 16 字节)

缺点:

  • ??? - 如果你能想到任何,请告诉我。

随之而来的一些后续问题:

  1. 我应该在末尾使用多于还是少于 6 个随机数字?它会对索引性能产生影响吗?

  2. 这些方法之一是否“随机”?:让 PHP 生成 6 位数字并将它们连接在一起 -VS- 让 PHP 生成 1 - 999999 范围内的数字,然后填充零以确保 6 位数字。

感谢您的任何提示。对文字墙感到抱歉。

【问题讨论】:

  • 我的问题here 提出了类似的建议,可能会让您对所涉及的考虑因素有一些进一步的了解。
  • 您应该查看birthday problem 以了解对于给定数量的生成值,您需要多少随机位才能将冲突机会保持在给定阈值以下。举一个例子,16 位(正确!)随机性,如果你创建 12 个值,你(已经)有大约 0.001% 的机会发生至少一次碰撞。换句话说,如果你想生成许多值,如果你想期望唯一性,你需要相当多位。
  • 使用 GUID 作为唯一索引,但 also calculate a 64-bit (BIGINT) hash of the GUID, store that in a separate NOT UNIQUE column, and index it。要检索,请查询与 both 列的匹配项 - 64 位索引应该可以提高效率。

标签: mysql sql database performance database-design


【解决方案1】:

我在职业生涯中遇到过这个问题。我们使用时间戳 + 随机数,当我们的应用程序扩展(更多客户端、更多服务器、更多请求)时遇到了严重问题。当然,我们(愚蠢地)只使用了 4 位数字,然后更改为 6,但您会惊讶于错误仍然发生的频率。

在足够长的时间段内,保证您会遇到重复的密钥错误。我们的应用程序是关键任务,因此即使由于固有的随机行为而失败的最小机会也是不可接受的。我们开始使用 UUID 来避免这个问题,并仔细管理它们的创建。

使用 UUID,您的索引大小会增加,而更大的索引会导致性能下降(可能不明显,但仍然更差)。然而 MySQL 支持原生 UUID 类型(永远不要使用 varchar 作为主键!!),并且即使与 bigint 相比,它也可以非常有效地处理索引、搜索等。对索引的最大性能影响几乎总是被索引的行数,而不是被索引的项目的大小(除非你想在长文本或类似的荒谬的东西上建立索引)。

回答您的问题:如果您不打算显着扩展您的应用程序/服务,Bigint(附带随机数)将是可以的。如果您的代码可以在没有太多改动的情况下处理更改,并且如果发生重复键错误,您的应用程序不会爆炸,那就去吧。否则,硬着头皮选择更实质性的选择。

您以后总是可以实现更大的更改,例如切换到完全不同的后端(我们现在面临...:P)

【讨论】:

  • 感谢您的回复科林。您的冲突问题是否仅在 (1) 您尝试合并来自多个服务器的数据时发生 - 或 - (2) 应用程序在其当前服务器上创建时未检查重复项?为了解决问题 1,{timestamp}{server_id}{random} 的格式可以解决这个问题。如果当前服务器上存在重复错误,则可以通过获取代码来创建新 ID 来解决问题 2(从技术上讲,即使使用 UUID,我认为应用程序也应该这样做)。
  • 据我所知,MySQL 没有 UUID 的列类型,只有一个生成值的函数。您将 UUID 存储在哪种类型的列中?我遇到的大多数论坛帖子都推荐 BINARY(16) 或 BINARY(36)。出于好奇,您要迁移到哪个数据库以及为什么?
  • 我们使用 Binary(16),实际上我们创建和管理 UUID 的方式与 bigint (dttmstamp + rand) 的想法几乎相同,尽管我们对 rand 部分做了更多它基本上是一个 16 字节的数字,而不是 8。我们正在研究 Hadoop/HDFS,因为我们的问题在 MapReduce 样式/上下文中相当容易解决,并且在可扩展性方面应该有所帮助。
  • 当您只使用 4 个随机数字时,您的平均行创建率大致是多少?
  • 只是出于好奇,这样的问题更适合程序员 stackexchange 吗?过去我曾在这里问过类似的问题,但结果却遭到了反对和气馁。
【解决方案2】:

您可以手动更改自动编号起始编号。

ALTER TABLE foo AUTO_INCREMENT = ####

一个无符号的 int 最多可以存储 4,294,967,295,让我们向下舍入到 4,290,000,000。

使用前 3 位作为服务器序列号,最后 7 位作为行 ID。

这为您提供了多达 430 台服务器(包括 000 台),以及每台服务器多达 1000 万个 ID。

因此对于服务器 #172,您手动将自动编号更改为从 1,720,000,000 开始,然后让它按顺序分配 ID。

如果您认为您可能拥有更多服务器,但每台服务器的 ID 更少,则将其调整为每台服务器 4 位数字和 6 位 ID(即最多 100 万个 ID)。

您还可以使用二进制数字而不是十进制数字来拆分数字(每个服务器可能有 10 个二进制数字,ID 为 22。例如,服务器 76 从 2^22*76 = 318,767,104 开始,以 322,961,407 结束)。

就此而言,您甚至不需要明确的拆分。将 4,294,967,295 除以您认为您将拥有的最大服务器数量,这就是您的间距。

如果您认为需要更多标识符,可以使用 bigint,但这是一个非常庞大的数字。

【讨论】:

  • 感谢您的回复。前段时间我确实想到了这个选项,但意识到将数据合并到具有较低 server_id 的服务器时可能会出现问题。假设我有一堆服务器,包括编号为 160 和 170 的服务器。如果我将服务器 170 的行导入到服务器 160,则表上的 auto_increment 值将设置为表中的最高 ID 号。因此服务器 160 不能继续分配 160xxxxxx 范围内的 ID。我只是对此做了一个小实验,虽然 MySQL 接受了将 auto_increment 值更改回来的命令,但它不需要。
  • 你需要制定一个规则,一旦你合并数据,旧服务器就会得到一个新的数字。然后,新服务器获取服务器编号较高的编号(或者也给它一个新编号)。您应该对其进行编码,以便更改服务器编号非常容易。
【解决方案3】:

使用 GUID 作为唯一索引,但计算 GUID 的 64 位 (BIGINT) 散列,将其存储在单独的 NOT UNIQUE 列中,并且索引它。要检索,请查询与 both 列的匹配项 - 64 位索引应该可以提高效率。

这样做的好处是哈希:
一种。不必是唯一的。
湾。可能分布良好。

成本:额外的 8 字节列及其索引。

【讨论】:

    【解决方案4】:

    如果您想使用时间戳方法,请执行以下操作:

    给每个服务器一个数字,附加正在执行插入的应用程序的进程 ID(或线程 ID)(在 PHP 中它是 getmypid()),然后附加该进程已经存活了多长时间/ active for(在 PHP 中是 getrusage()),最后在每个脚本调用开始时添加一个从 0 开始的计数器(即,同一脚本中的每个插入都添加一个)。

    此外,您不需要存储完整的 unix 时间戳 - 这些数字中的大多数用于说明它是 2011 年,而不是 1970 年。因此,如果您无法获得一个数字来说明该过程存在多长时间,然后至少减去代表今天的固定时间戳 - 这样您需要的数字会少得多。

    【讨论】:

    • 这增加了多年来发生碰撞的机会。
    • @Aredridel 不确定你的意思。它没有。你减去一个固定的数字,而不是除或四舍五入。
    • 啊,有道理。只需将纪元重置为更接近现在。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-10
    • 1970-01-01
    • 1970-01-01
    • 2014-10-28
    • 2012-12-21
    • 2023-04-09
    • 1970-01-01
    相关资源
    最近更新 更多