在 MySQL 中使用 UUID 作为主键的存在问题及如何优化?
itomcoil 2025-09-14 20:02 3 浏览
在分布式架构中,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
相关推荐
- MySQL中的MVCC到底能不能解决幻读
-
在MySQL当中,只有使用了InnoDB存储引擎的数据库表才支持事务。有了事务就可以用来保证数据的完整以及一致性,保证成批的SQL语句要么全部执行,要么全部不执行。事务用来管理insert、updat...
- 每次写SQL时总忘记语法顺序怎么办,这里一招教你解决
-
MySQL基础(五)-----表达式&函数和分组查询表达式和函数:表达式就是将数字和运算符连接起来的组合,称之为表达式,比如:1+1;函数就是系统自带已经定义好可以直接使用的函数,例如MAX,MIN;...
- 在 MySQL 中使用 UUID 作为主键的存在问题及如何优化?
-
在分布式架构中,UUID(通用唯一标识符)因其能够确保全球唯一性而广泛应用。它不依赖于数据库的自增机制,特别适合于多个系统间的数据同步。然而,尽管UUID提供了很多优势,直接使用它作为MySQL...
- SQL入门知识篇_sql入门教程
-
一、什么是数据库?什么是SQL?1、数据库:存放数据,可以很多人一起使用2、关系数据库:多张表+各表之间的关系3、一张表需要包含列、列名、行4、主键:一列(或一组列),其值能够唯一区分表中的每个行。5...
- MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)
-
目录1.索引基础2.索引类型2.1哈希索引2.2有序数组2.3B+树索引(InnoDB)3.联合索引4.最左前缀原则5.覆盖索引6.索引下推总结:1.索引基础索引对查询的速度有着至...
- Mysql索引覆盖_mysql索引ref
-
作者:京东零售孙涛1.什么是覆盖索引通常情况下,我们创建索引的时候只关注where条件,不过这只是索引优化的一个方向。优秀的索引设计应该纵观整个查询,而不仅仅是where条件部分,还应该关注查询所包...
- MySQL常用语句汇总_mysql常用语句大全
-
一、背景日常测试开发工作中会用到各类SQL语句,很多时候都是想用的时候才发现语句细节记不清楚了,临时网上搜索SQL语法,挺费时费力的,语法还不一定是对的。因此汇总整理了一下MySQL最常用的各类语句,...
- POI批量生成Word文档表格_poi批量导入excel
-
前言 当我们在写设计文档,或者是其他涉及到数据架构、表结构时,可以用POI来批量生成表格,例如下面的表格 代码编写 引入POI依赖<!--引入apachepoi-...
- cmd命令操作Mysql数据库,命令行操作Mysql
-
Mysql数据库是比较流行的数据库之一,维基百科的介绍如下:MySQLisanopen-sourcerelationaldatabasemanagementsystem(RDBMS)....
- MySQL大数据表处理策略,原来一直都用错了……
-
场景当我们业务数据库表中的数据越来越多,如果你也和我遇到了以下类似场景,那让我们一起来解决这个问题。数据的插入,查询时长较长后续业务需求的扩展,在表中新增字段,影响较大表中的数据并不是所有的都为有效数...
- SQL点滴(查询篇):数据库基础查询案例实战
-
本文主要是对微头条SQL小技能的汇总,便于收藏查阅,为数据库初学者提供多快好省又可实际操作的帮助。下面为正文。1.通用*查询在从数据库表中检索所有行与列,若要查询所有数据,通常做法为:select*...
- Mysql学习笔记-InnoDB深度解析_mysql innodb底层原理
-
前言我们在上一篇博客聊了Mysql的整体架构分布,连接层、核心层、存储引擎层和文件系统层,其中存储引擎层作为MysqlServer中最重要的一部分,为我们sql交互提供了数据基础支持。存储引擎和文件...
- 「MySQL调优」大厂MySQL性能优化实战讲解
-
WhyPerformance在1990s,人们还使用拨号接入互联网的时候,浏览一个网页或加入一个线上聊天室需要几分钟的时间去加载是一件很正常的事情。而2009年Akamai公司的报告显示,如果一个网...
- MySQL数据库性能优化_mysql数据库优化及sql调优
-
任何软件平台的运行都需要依赖于数据库的存储,数据库存储着业务系统的关键信息,包含基础的组织、人员及业务流程板块信息等。因此在平台运转过程中,数据库的响应速率直接影响平台的回显速度及用户的友好体验。尽管...
- 面试中的老大难-mysql事务和锁,一次性讲清楚
-
什么是事务在维基百科中,对事务的定义是:事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。事务的四大特性事务包含四大特性,即原子性(Atomicity)、一致性...
- 一周热门
- 最近发表
- 标签列表
-
- ps图案在哪里 (33)
- super().__init__ (33)
- python 获取日期 (34)
- 0xa (36)
- super().__init__()详解 (33)
- python安装包在哪里找 (33)
- linux查看python版本信息 (35)
- python怎么改成中文 (35)
- php文件怎么在浏览器运行 (33)
- eval在python中的意思 (33)
- python安装opencv库 (35)
- python div (34)
- sticky css (33)
- python中random.randint()函数 (34)
- python去掉字符串中的指定字符 (33)
- python入门经典100题 (34)
- anaconda安装路径 (34)
- yield和return的区别 (33)
- 1到10的阶乘之和是多少 (35)
- python安装sklearn库 (33)
- dom和bom区别 (33)
- js 替换指定位置的字符 (33)
- python判断元素是否存在 (33)
- sorted key (33)
- shutil.copy() (33)