MySQL中是否可以使用UUID作为主键?

AI 概述
UUID 的多个版本UUIDv1UUIDv2UUIDv3 和 UUIDv5UUIDv4UUIDv6UUIDv7UUIDv8UUID 和 MySQL插入性能更高的存储利用率MySQL 中使用 UUID 主键的最佳实践使用二进制数据类型使用有序 UUID 变体使用 MySQL 的内置 UUID 函数使用替代的 ID 类型总结 全局唯一标识符(UUID ),是一种允许开发者生成全局唯一 ID ...
目录
文章目录隐藏
  1. UUID 的多个版本
  2. UUID 和 MySQL
  3. MySQL 中使用 UUID 主键的最佳实践
  4. 总结

全局唯一标识符(UUID ),是一种允许开发者生成全局唯一 ID 的方法,其设计确保了无需了解其他系统即可保证 ID 的独特性。

在分布式架构中,多个系统和数据库负责记录的创建,UUID 格外有用。或许,你可能认为将 UUID 用作数据库主键是一个不错的主意,但如果使用不当,它可能会严重影响数据库性能。

在本文中,我们将详细剖析在 MySQL 数据库中使用 UUID 作为主键的弊端,在这个过程中也详细的了解一下 UUID 多个版本构成。

UUID 的多个版本

截至目前,UUID 有五个正式版本与三个提议版本。让我们来看每个版本的工作原理以更好地理解它们的不同。

UUIDv1

UUID 版本 1 是基于时间的 UUID,其结构如下:

UUIDv1 结构图
UUIDv1 结构图

尽管现代计算大多使用 UNIX 时间戳 (从 1970 年 1 月 1 日开始) 作为基础,UUID 实际上使用了另一个时间起点 —— 1568 年 10 月 10 日,这一天是格里高利历 开始被广泛使用的时间。

UUID 中嵌入的时间戳从这一日期起每 100 纳秒递增,用于设置 time_lowtime_mid 和 time_hi 部分。

UUID 的第三部分包含了版本号以及 time_hi,且该部分的第一个字符始终标志着该 UUID 的版本。这种结构适用于所有版本的 UUID(后续示例中也会指出)。保留部分称为 UUID 的变体,它决定了 UUID 内部的位使用方式。最后一部分是 node,表示生成 UUID 的系统的唯一地址。

UUIDv2

与版本 1 相比,UUID 版本 2 对结构做出了一些改变,将 low_time 部分替换为 POSIX 用户 ID。

理论上讲这种 UUID 可以追溯到生成它的用户账户。但由于 low_time 是 UUID 变动性的重要来源,替换这个部分会增加发生冲突的可能性。因此,这个版本很少被使用。

UUIDv3 和 UUIDv5

UUID 的版本 3 和版本 5 非常相似。这些版本旨在以确定性方式生成 UUID,也就是说,给定相同的信息能够生成相同的 UUID。

这些实现使用两种信息:一个命名空间(本身就是一个 UUID)和一个名称。这些值通过哈希算法生成 128 位的值并表示为 UUID。

两者的主要区别在于:版本 3 使用 MD5 哈希算法,而版本 5 使用 SHA1 。

UUIDv4

版本 4 被称为随机变体,因为正如其名称所示,UUID 的值几乎完全随机。不过,第一个例外是 UUID 的第三部分第一个字符,它始终是 4,用来标志 UUID 使用的是版本 4。

UUIDv4 结构图
UUIDv4 结构图

UUIDv6

版本 6 与版本 1 几乎相同,唯一不同是记录时间戳的位顺序被翻转了,将时间戳中最重要的部分放置优先。以下图表展示了它们的区别:

UUIDv6 结构图
UUIDv6 结构图

这么做的主要原因是生成一个与版本 1 兼容的值,同时使这些值更具排序性,因为时间戳最重要的部分被放在前面。

UUIDv7

版本 7 也是基于时间的 UUID 变体,但它使用了现代更常见的 Unix 时间戳 (而不是版本 1 中的格里高利日期)。另一个主要不同点是,将表示生成 UUID 系统的 node 替换为随机值,使 UUID 更难追溯到其来源。

UUIDv8

版本 8 是最新版本,它允许厂商特定的实现,同时遵循 RFC 标准。版本 8 的唯一要求是,版本号必须被指定在第三部分的第一个位置,这点和所有其他版本一致。

UUID 和 MySQL

UUID(大多数情况下)在分布式架构中保证跨系统的唯一性,因此你可能会倾向将其用作记录的主键。然而,与自增整数相比,这样做有一些显而易见的权衡。

插入性能

每当向 MySQL 表插入新记录时,与主键关联的索引需要更新以让查询变得高效。MySQL 中的索引以 B+树 的形式存在,这是一种多层数据结构,能让查询快速找到所需数据。

以下图示展示了一个包含 1 到 6 的简单 B-树结构。如果一个查询要求查找值 5,MySQL 从 root 节点开始,将沿右侧路径遍历树以找到目标值。

注: 图示使用的是 B-树 而不是 B+树。两者的主要区别在于,B+树的叶节点包含对实际数据的引用,而 B-树的叶节点则不包含。

B-树结构图
B-树结构图

当新值(例如 7 到 9)被添加时,MySQL 会拆分右侧节点并重新平衡树结构。

B-树结构图

这种过程被称为 页拆分(page splitting) ,其目标是保持 B+树结构的平衡,以便 MySQL 能快速找到所需数据。对于连续值,这种过程相对简单;然而当算法中引入随机性时,MySQL 的树平衡工作可能显著变慢。在一个高流量的数据库上,这可能会影响用户体验,因为 MySQL 试图保持树结构平衡。

更高的存储利用率

MySQL 中的所有主键都是索引。默认情况下,一个自增整数的存储空间为 32 位 。相比之下,UUID 的存储不论是二进制形式还是字符串形式,其占用空间均远大于整数:

  • UUID 的二进制形式需要 128 位 ,是 32 位整数的 4 倍;
  • 字符串形式,通常为 CHAR(36),需要 288 位 ,是 32 位整数的 9 倍

此外,除主键上的默认索引外,次级索引也会占用更多空间。次级索引中使用主键作为指向实际行的指针,意味着主键也需要存储在索引中。如果表中使用 UUID 作为主键,这会显著增加数据库的存储需求,具体取决于表中创建了多少索引。

最后,随机性引发的 页拆分 (如前所述)也会对存储使用和性能造成负面影响。InnoDB 假定主键可以以数字或字典顺序预测性地递增。如果主键是顺序的,InnoDB 会填满页的 94% ,然后创建新页。然而,如果主键是随机的,利用率可能会低至 50% 。因此,使用包含随机性的 UUID 可能导致索引占用过多页资源。

MySQL 中使用 UUID 主键的最佳实践

如果确实需要在表中使用 UUID 作为记录的唯一标识符,可以遵循以下最佳实践来尽量减少负面影响:

使用二进制数据类型

虽然 UUID 经常以 36 字符长的字符串 表示,但它们也可以表示为原生的二进制形式。如果将 UUID 转换为二进制格式,存储在 BINARY(16) 列中,每个值的存储需求将减少到 16 字节 。虽然仍明显大于 32 位整数,但已经远好于存储为 CHAR(36)

CREATE TABLE uuids(
  UUIDAsChar CHAR(36) NOT NULL,
  UUIDAsBinary BINARY(16) NOT NULL
);

INSERT INTO uuids SET
  UUIDAsChar = 'd211ca18-d389-11ee-a506-0242ac120002',
  UUIDAsBinary = UUID_TO_BIN('d211ca18-d389-11ee-a506-0242ac120002');

SELECT * FROM uuids;
-- +--------------------------------------+------------------------------------+
-- | UUIDAsChar                           | UUIDAsBinary                       |
-- +--------------------------------------+------------------------------------+
-- | d211ca18-d389-11ee-a506-0242ac120002 | 0xD211CA18D38911EEA5060242AC120002 |
-- +--------------------------------------+------------------------------------+

使用有序 UUID 变体

使用支持 排序 的 UUID 版本可以减少使用 UUID 带来的性能和存储影响,因为生成的值更接近于顺序状态,从而避免部分页拆分问题。

即使在多个系统间生成,基于时间的 UUID(例如版本 6 或版本 7)也能保证唯一性,同时尽量保持顺序。有一个例外是 UUID 版本 1,它将时间戳中最不重要部分放置优先。

使用 MySQL 的内置 UUID 函数

MySQL 支持直接在 SQL 中生成 UUID,但仅支持版本 1 的值。尽管单独使用它们不是最佳做法,但 MySQL 提供了一个名为 uuid_to_bin 的辅助函数,不仅能将字符串值转换为二进制,还支持使用 “swap flag”(交换标志),使生成的二进制值更具顺序性。

SET @uuidvar = 'd211ca18-d389-11ee-a506-0242ac120002';

-- 不使用 swap flag
SELECT HEX(UUID_TO_BIN(@uuidvar)) AS UUIDAsHex;
-- +----------------------------------+
-- | UUIDAsHex                        |
-- +----------------------------------+
-- | D211CA18D38911EEA5060242AC120002 |
-- +----------------------------------+

-- 使用 swap flag
SELECT HEX(UUID_TO_BIN(@uuidvar,1)) AS UUIDAsHex;
-- +----------------------------------+
-- | UUIDAsHex                        |
-- +----------------------------------+
-- | 11EED389D211CA18A5060242AC120002 |
-- +----------------------------------+

使用替代的 ID 类型

UUID 不是分布式架构中唯一提供唯一性保障的标识符格式。自 UUID 于 1987 年首次创建以来,已经提出了许多不同的格式,例如 Snowflake ID、ULID 或 NanoID (PlanetScale 使用 NanoID)。

# Snowflake ID
7167350074945572864

# ULID
01HQF2QXSW5EFKRC2YYCEXZK0N

# NanoID
kw2c0khavhql

总结

在 MySQL 中使用 UUID 主键在分布式系统中可以几乎确保唯一性;然而它也带来了显著的权衡。幸运的是,通过多个版本的 UUID 以及其替代选项,可以找到更好应对这些权衡的方法,可根据具体的业务场景来选择更好的实践方案。

以上关于MySQL中是否可以使用UUID作为主键?的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » MySQL中是否可以使用UUID作为主键?

发表回复