在 MySQL 中使用 UUID 作为主键的存在问题及如何优化?
csdh11 2025-03-26 11:13 33 浏览
在分布式架构中,UUID(通用唯一标识符)因其能够确保全球唯一性而广泛应用。它不依赖于数据库的自增机制,特别适合于多个系统间的数据同步。然而,尽管 UUID 提供了很多优势,直接使用它作为 MySQL 表的主键可能会带来性能上的问题。
本文将详细探讨在 MySQL 中使用 UUID 作为主键的缺点,并分享一些优化方法,以尽量减少其对性能和存储的影响。
UUID 版本简介
UUID 目前有多个版本,其中不同的版本有不同的特性和用途。理解这些版本有助于我们选择最适合的 UUID 形式,以减轻性能负担。
UUID v1:基于时间的 UUID
UUID v1 使用当前时间戳和硬件地址(如 MAC 地址)来生成唯一标识符。它的特点是包含了时间信息,适合用作按时间顺序生成的唯一 ID。
虽然许多现代计算使用 UNIX 纪元时间(1970 年 1 月 1 日)作为基础,但 UUID 实际上使用不同的日期 1568 年 10 月 10 日,这是公历开始得到更广泛使用的日期。UUID 中嵌入的时间戳从该日期开始以 100 纳秒为增量增长,然后用于设置 UUID 的 time_low 、 time_mid 和 time_hi 段。
UUID 的第三段包含 version 以及 time_hi 并占据该段的第一个字符。对于所有版本的 UUID 都是如此,如后续示例所示。 reserved 部分也称为 UUID 的变体,它决定如何使用 UUID 中的位。最后,UUID 的最后一段是 node ,它是生成 UUID 的系统的唯一地址。
UUIDv2:基于 POSIX 用户 ID
UUID v2 在 v1 的基础上做了修改,使用 POSIX 用户 ID 替代了时间部分。这个版本较少被使用,因其增加了冲突的可能性。
UUID v3 和 v5:基于名称的 UUID
这两个版本的 UUID 通过对命名空间和名称进行哈希(v3 使用 MD5,v5 使用 SHA1)来生成唯一标识符。它们适用于生成可预测的、基于相同输入数据的 UUID。
UUID v4:随机 UUID
UUID v4 是最常见的 UUID 版本,完全基于随机数生成。它的优势在于生成简单、没有时间信息,但由于其完全随机性,可能会对数据库索引产生较大影响。
UUIDv6:时间戳优先 UUID
UUIDv6 与 UUIDv1 几乎相同,唯一的区别在于它对时间戳的存储方式做了调整。具体来说,UUIDv6 将时间戳的最重要部分放在前面,而不是像 UUIDv1 那样将其放在后面。这样做的目的是为了更好地排序,并且使生成的 UUID 更加适用于数据库等需要快速插入的场景。
下图展示了这两种版本的差异。
通过这种方式,UUIDv6 在保留与 UUIDv1 兼容性的同时,也提升了排序性能,因为时间戳的最重要部分被优先存储。
UUIDv7:基于 Unix 时间戳的 UUID
UUIDv7 也是基于时间戳的 UUID 变体,但它使用了更常见的 Unix Epoch 时间戳,而不是 UUIDv1 中使用的公历日期。与 UUIDv1 相比,UUIDv7 的另一个关键区别是其节点部分,UUIDv7 不再使用基于硬件地址的节点,而是用随机值替代。这使得 UUIDv7 更加难以追溯到生成它的系统,从而提高了隐私性。
UUIDv8:供应商特定的 UUID
UUIDv8 是目前最新的 UUID 版本,它允许特定于供应商的实现,同时仍然遵循 RFC 标准。UUIDv8 的要求与其他版本类似,在其第三段的第一个位置明确指定版本号。不同的是,UUIDv8 的设计更灵活,可以根据需求进行自定义,适用于特定的使用场景。
UUID 在 MySQL 中的挑战
尽管 UUID 在分布式系统中具有全球唯一性的优势,但直接将 UUID 用作 MySQL 主键时,可能会带来以下问题:
1. 性能问题:Insert 性能下降
MySQL 中的主键默认会创建索引,通常使用 B+ 树结构。每次插入数据时,主键索引都会更新,保证数据按顺序排列。对于自动递增的整数主键,数据插入是按顺序进行的,B+ 树的结构不会频繁发生重平衡。然而,UUID 是随机的,这意味着每次插入时,MySQL 都需要调整 B+ 树结构,频繁发生页面拆分(page split),导致性能下降。
每当一条新记录插入到 MySQL 的表中,与主键关联的索引都需要更新,以便查询表的性能。MySQL 中的索引采用 B+ 树的形式,这是一种多层数据结构,允许查询快速找到所需的数据。
下图演示了此结构的相对简单版本,其中有 6 个条目,值从 1 到 6。如果查询请求 5 ,MySQL 将从根节点开始,并从那里知道:它必须沿着树的右侧遍历才能找到它要找的东西。
为简单起见,这些图显示 B 树而不是 B+ 树。主要区别在于,在 B+ Tree 中,叶节点包含对实际数据的引用,而在 B-Tree 中,叶节点不包含对实际数据的引用。
如果添加值 7-9,MySQL 将拆分右侧节点并重新平衡树。
这个过程称为页拆分,目标是保持 B+ Tree 结构平衡,以便 MySQL 能够快速找到它要查找的数据。对于顺序值,这个过程相对简单;然而,当算法中引入随机性时,MySQL 重新平衡树可能需要更长的时间。在大容量数据库上,这可能会损害用户体验,因为 MySQL 会尝试保持树平衡。
2.更高的存储利用率
MySQL 中的所有主键均已建立索引。默认情况下,自动递增整数每个值将消耗 32 位存储空间。将此与 UUID 进行比较。如果以紧凑的二进制形式存储,单个 UUID 将占用磁盘上的 128 位。这已经是 32 位整数消耗的 4 倍。相反,如果您选择使用更易读的基于字符串的表示形式,则每个 UUID 都可以存储为 CHAR(36) ,每个 UUID 消耗高达 288 位的数据。这意味着每条记录将存储比 32 位整数多 9 倍的数据。
除了在主键上创建的默认索引外,二级索引也会消耗更多的空间。这是因为二级索引使用主键作为指向实际行的指针,这意味着它们需要与索引一起存储。这可能会导致数据库的存储要求显著增加,具体取决于使用 UUID 作为主键的表上创建的索引数量。
最后,页面分割(如上一节所述)也会对存储利用率和性能产生负面影响。InnoDB 假设主键将按数字或字典顺序按可预测的方式递增。如果为 true,InnoDB 将在创建新页面之前将页面填充到页面大小的 94% 左右。当主键是随机的时,每个页面所使用的空间量可以低至 50%。因此,使用包含随机性的 UUID 可能会导致过度使用页面来存储索引。
在 MySQL 中使用 UUID 主键的最佳方法
如果您绝对需要使用 UUID 作为表中记录的唯一标识符,您可以遵循一些最佳实践,以最大程度地减少这样做的负面影响。
1. 使用二进制数据类型
虽然 UUID 通常表示为 36 个字符的字符串,但它也可以以其本机二进制格式存储。如果将 UUID 转换为二进制值,可以将其存储在 BINARY(16) 列中,这将每个 UUID 的存储要求减少到 16 个字节。这仍然比 32 位整数大一些,但比将 UUID 存储为 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 |
-- +--------------------------------------+------------------------------------+
2. 使用有序的 UUID 变体
使用支持排序的 UUID 版本可以使生成的值更加连续,从而减少前面提到的页面拆分问题,从而减轻使用 UUID 带来的性能和存储负担。即使这些 UUID 是在多个系统上生成的,基于时间的 UUID(例如版本 6 或 7)仍能保证唯一性,并保持尽可能的顺序性。UUIDv1 是个例外,它的最低有效部分首先包含时间戳。
3. 使用内置的 MySQL UUID 函数
MySQL 支持在 SQL 中直接生成 UUID,但它只支持 UUIDv1 类型。尽管单独使用它们并不理想,但 MySQL 提供了一个名为 uuid_to_bin 的辅助函数。该函数不仅可以将 UUID 字符串转换为二进制格式,还可以使用 "swap 标志" 重新排序时间戳部分,使生成的二进制 UUID 更加连续。
set @uuidvar = 'd211ca18-d389-11ee-a506-0242ac120002';
-- Without swap flag
SELECT HEX(UUID_TO_BIN(@uuidvar)) as UUIDAsHex;
-- +----------------------------------+
-- | UUIDAsHex |
-- +----------------------------------+
-- | D211CA18D38911EEA5060242AC120002 |
-- +----------------------------------+
-- With swap flag
SELECT HEX(UUID_TO_BIN(@uuidvar,1)) as UUIDAsHex;
-- +----------------------------------+
-- | UUIDAsHex |
-- +----------------------------------+
-- | 11EED389D211CA18A5060242AC120002 |
-- +----------------------------------+
4. 使用备用 ID 类型
UUID 并不是唯一性的唯一标识符。在分布式架构中,已经有其他标识符类型被提出并得到广泛应用,如 Snowflake ID、ULID,甚至 NanoID(我们在 PlanetScale 中使用的就是这种 ID)。这些替代方案有时在性能和存储上会优于传统的 UUID。
# Snowflake ID
7167350074945572864
# ULID
01HQF2QXSW5EFKRC2YYCEXZK0N
# NanoID
kw2c0khavhql
结论
在 MySQL 中使用 UUID 作为主键可以(几乎)保证分布式系统中的唯一性,但这也伴随着一定的权衡。幸运的是,有多种 UUID 变体和替代方案可以帮助解决这些问题。
参考:https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql
相关推荐
- NUS邵林团队发布DexSinGrasp基于强化学习实现物体分离与抓取统一
-
本文的作者均来自新加坡国立大学LinSLab。本文的共同第一作者为新加坡国立大学实习生许立昕和博士生刘子轩,主要研究方向为机器人学习和灵巧操纵,其余作者分别为硕士生桂哲玮、实习生郭京翔、江泽宇以及...
- 「PLC进阶」如何通过编写SCL语言程序实现物料分拣?
-
01、前言SCL作为IEC61131-3编程语言的一种,由于其高级语言的特性,特别适合复杂运算、复杂数学函数应用的场合。本文以FactoryIO软件中的物料分拣案例作为硬件基础,介绍如何通过SCL来实...
- zk源码—5.请求的处理过程一(http1.1请求方法)
-
大纲1.服务器的请求处理链...
- 自己动手从0开始实现一个分布式 RPC 框架
-
前言为什么要自己写一个RPC框架,我觉得从个人成长上说,如果一个程序员能清楚的了解RPC框架所具备的要素,掌握RPC框架中涉及的服务注册发现、负载均衡、序列化协议、RPC通信协议、Socket通信、异...
- MLSys’25 | 极低内存消耗:用SGD的内存成本实现AdamW的优化性能
-
AIxiv专栏是机器之心发布学术、技术内容的栏目。过去数年,机器之心AIxiv专栏接收报道了2000多篇内容,覆盖全球各大高校与企业的顶级实验室,有效促进了学术交流与传播。如果您有优秀的工作想要分享,...
- 线程池误用导致系统假死(线程池会自动销毁吗)
-
背景介绍在项目中,为了提高系统性能使用了RxJava实现异步方案,其中异步线程池是自建的。但是当QPS稍微增大之后却发现系统假死、无响应和返回,调用方出现大量超时现象。但是通过监控发现,系统线程数正常...
- 大型乘用车工厂布局规划(六大乘用车基地)
-
乘用车工厂的布局规划直接影响生产效率、物流成本、安全性和未来扩展能力。合理的布局应确保生产流程顺畅、物流高效、资源优化,并符合现代化智能制造和绿色工厂的要求。以下是详细的工厂布局规划要点:1.工厂布...
- 西门子 S7-200 SMART PLC 连接Factory IO的方法
-
有很多同学不清楚如何西门子200smart如何连接FactoryIO,本教程为您提供了如何使用西门子S7-200SMARTPLC连接FactoryIO的说明。设置PC和PLC之间的...
- 西门子博图高级仿真软件的应用(西门子博途软件仿真)
-
1.博图高级仿真软件(S7-PLCSIMAdvancedV2.0)S7-PLCSIMAdvancedV2.0包含大量仿真功能,通过创建虚拟控制器对S7-1500和ET200SP控制器进行仿真...
- PLC编程必踩的6大坑——请对号入座,评论区见
-
一、缺乏整体规划:面条式代码问题实例:某快递分拣线项目初期未做流程图设计,工程师直接开始编写传送带控制程序。后期增加质检模块时发现I/O地址冲突,电机启停逻辑与传感器信号出现3处死循环,导致项目延期2...
-
- 统信UOS无需开发者模式安装软件包
-
原文链接:统信UOS无需开发者模式安装软件包...
-
2025-05-05 14:55 csdh11
- 100个Java工具类之76:数据指纹DigestUtils
-
为了提高数据安全性,保证数据的完整性和真实性,DigestUtils应运而生。正确恰当地使用DigestUtils的加密算法,可以实现数据的脱敏,防止数据泄露或篡改。...
- 麒麟KYLINIOS软件仓库搭建02-软件仓库添加新的软件包
-
#秋日生活打卡季#原文链接:...
- Java常用工具类技术文档(java中工具类的作用)
-
一、概述Java工具类(UtilityClasses)是封装了通用功能的静态方法集合,能够简化代码、提高开发效率。本文整理Java原生及常用第三方库(如ApacheCommons、GoogleG...
- 软路由的用法(自动追剧配置)(软路由教学)
-
本内容来源于@什么值得买APP,观点仅代表作者本人|作者:值友98958248861环境和需求...
- 一周热门
- 最近发表
- 标签列表
-
- mydisktest_v298 (34)
- document.appendchild (35)
- 头像打包下载 (61)
- acmecadconverter_8.52绿色版 (39)
- word文档批量处理大师破解版 (36)
- server2016安装密钥 (33)
- mysql 昨天的日期 (37)
- parsevideo (33)
- 个人网站源码 (37)
- centos7.4下载 (33)
- mysql 查询今天的数据 (34)
- intouch2014r2sp1永久授权 (36)
- 先锋影音源资2019 (35)
- jdk1.8.0_191下载 (33)
- axure9注册码 (33)
- pts/1 (33)
- spire.pdf 破解版 (35)
- shiro jwt (35)
- sklearn中文手册pdf (35)
- itextsharp使用手册 (33)
- 凯立德2012夏季版懒人包 (34)
- 冒险岛代码查询器 (34)
- 128*128png图片 (34)
- jdk1.8.0_131下载 (34)
- dos 删除目录下所有子目录及文件 (36)