在 MariaDB 中始终使用正确的 UUID
以下文章由 Stefano Petrilli 撰写,他为 MariaDB 贡献了 UUIDv4 和 UUIDv7 的实现。谢谢你,Stefano!
通用唯一标识符 (UUID) 的最初版本(现称为 UUIDv1)于 20 世纪 80 年代首次出现。它们提供的最有趣的保证是生成在空间和时间上始终唯一的 ID。
为了遵守这一承诺,它使用了三个元素的组合
- 节点,这是一个用于标识生成 UUID 的机器的字段。历史上,此字段使用 IEEE MAC 地址设置。Mac 地址本身是分配给任何物理网络接口控制器的唯一标识符。这些代表了 UUIDv1 的前 48 位。
- 时间戳,它计算从 UUIDv1 生成的时刻到 1582 年 10 月 15 日(格里高利历的开始)之间的 100 纳秒间隔数。
- 一个时钟字段,用于区分在同一时间戳生成的 UUIDv1 或时钟被向后设置的情况。
此外,它还将 6 位设置为特定值,以标识它是 UUIDv1。

UUIDv1 的组成方式确保了空间-时间唯一性,这意味着如果 UUIDv1 根据规范生成,两次生成相同的 UUIDv1 几乎是不可能的。实际上,这意味着,例如,当使用 UUIDv1 作为主键时,任意数量的服务器可以将记录添加到同一表中,而无需协调以避免冲突。MariaDB 提供了一个函数来生成此类 UUID,即 uuid()
。
虽然理论上 UUIDv1 的实现是可靠的,但当它与现实世界的复杂性冲突时,它最终会暴露出一些局限性。
最明显的是需要 IEEE MAC 地址。对于没有网络接口的设备,以及虚拟化和容器化的出现,拥有物理 MAC 地址不再是理所当然的。最新的 UUID RFC 中提出的解决方案以及 MariaDB 的 UUIDv1 实现中实际使用的方法是随机生成 MAC 地址。由于在现代环境下很难强制执行 UUIDv1 的空间唯一性组件,我们只剩下时间唯一性,这意味着实际上野外可能发生 UUIDv1 冲突,尽管几率极低,我们仍然可以假定任何生成的 UUIDv1 都是唯一的。
一个更重要的问题是 UUIDv1 泄露了两个敏感信息:生成 UUIDv1 的机器的 MAC 地址和生成时的时间戳。MAC 地址为多种漏洞打开了大门,例如 MAC 欺骗和 MAC 泛洪,而时间戳与特定事件或信息关联时,可能会泄露有关用户或系统的敏感信息。除此之外,UUIDv1 在一定程度上是可以预测的。这种可预测性也可能被用于攻击,例如 三明治攻击。
主要是因为在现代环境中无法保证永远不会发生冲突,以及出于之前提到的安全风险,UUIDv1 被认为是遗留版本,并提出了克服其局限性的新版本。为了跟进这些变化,我提交了两个拉取请求,启动了最终使 UUIDv4 和 UUIDv7 在 MariaDB 中可用的工作。用于生成两种新 UUID 的函数是 uuid_v4()
和 uuid_v7()
。
UUIDv4 是解决因泄露 MAC 地址和创建时间戳而带来的安全问题的解决方案。与所有其他版本一样,此版本也将 6 位设置为特定的版本和修订,然后随机生成所有剩余的 122 位。

使用 UUIDv4 时仍可能发生冲突,但假设使用了良好种子的随机数生成器,两次生成相同 122 位序列的概率极低,我们可以假定冲突永远不会发生。可以创建的不同元素的数量是一个天文数字,即 2122 个唯一 UUIDv4。人类很难理解如此大的数字,但是,为了感知它有多大,如果我们为地球上的每一粒沙子分配一个 UUIDv4,我们甚至还没有用到 UUIDv4 总数的 0.01%。
UUIDv4 相对于 UUIDv1 是一个很大的改进,但它不是万能药。UUIDv4 与 UUIDv1 共有的主要问题是生成的 UUID 是随机分布的,而不是自然排序的。在数据库环境中,这可能会在多种情况下影响性能。通常,数据库内部使用的数据结构不擅长处理随机插入,这会导致非连续的内存访问和潜在的磁盘 I/O 增加。除此之外,一些内部指令依赖于自然顺序,从自然顺序会降低性能的位置开始。这会影响范围查询(Range Queries)、窗口函数(Window Functions)以及 ORDER BY
和 GROUP BY
等修饰符的性能。
在泄露时间戳不构成漏洞问题的用例中,例如 UUID 仅在内部使用且从未暴露给用户的情况,我们可以通过使用 UUIDv7 获得自然顺序,从而提高性能。

RFC 中所述,UUIDv7 可以通过三种不同的方式生成。我们在 MariaDB 中采用的实现是第三种版本,其中:UUIDv7 的前 60 位使用具有亚毫秒精度的 Unix 时间戳填充,6 位保留用于指定 UUID 的版本和修订,剩余的 62 位用随机数据填充。
总之,使用函数 uuid_v4()
和 uuid_v7()
生成的新提出的 UUID 版本优于遗留的 UUIDv1,UUIDv1 由于其局限性应避免使用。对于优先考虑性能且 UUID 不对外暴露的用例,推荐选择 UUIDv7。当安全或防止私人数据暴露至关重要时,UUIDv4 是更好的选择。从 MariaDB 11.7 开始,UUIDv4 和 UUIDv7 与 UUIDv1 一同提供,使您可以根据特定用例选择最佳版本。