mysql 存储uuid,MySQL中存储UUID的最佳实践

探讨了MySQL中UUID作为主键的问题,包括长度和格式导致的性能影响,并提出使用BINARY(16)类型及重组UUID顺序的方法来提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在MySQL中有一个UUID () 函数,通常用UUID做唯一标识,需要在数据库中进行存储。使用此函数可以让MySQL生成一个UUID值,并以VARCHAR(36)类型的可读形式返回。如图1:

8ffc54af7ed32416d9ce62f093cf0c71.png

图1

UUID值是非常随机的,因此常常被用来当做主键值(PRIMARY KEY),而且这些以UUID作为主键的数据可以很容易的从不同的数据库中汇聚到一起。但是对于像MySQL的InnoDB存储引擎来说,使用UUID作为主键(PRIMARY KEY)会带来一些问题。

1、问题阐述

问题一:UUID的长度问题

UUID的长度为36个字符。假设数据库的字符集为UTF8,那么UUID的最大长度为2+3*26=110字节。如果这样的UUID作为主键的话,不仅会是主键的尺寸很大,而且会使二级索引的尺寸变大,原因是MySQL中的二级索引的value存的是PRIMARY KEY。由于主键和二级索引的尺寸很大,所以不利于在内存中操作

问题二:UUID的格式问题

MySQL的UUID ()使用的是version 1的UUID,该类型的UUID的特点是基于时间,它是一个128位的数字,由5个十六进制数字组成的utf8字符串表示,我们以图1中的UUID值为例:

432a4ec8-3642-11e9-805a-0050568238b5,每对字符实际上是一个在00-FF范围内的十六进制数; 总共有16个数字,前三个数字432a4ec8-3642-11e9是由时间戳生成。但是,最左边的组变化最快(每微秒10次)。我们可以验证,如图2

c182e86726b8929da7c991a2eaf70f7a.png

图2

因为UUID是不连续的随机数,所以insert操作是随机的,数据被离散存储,造成innodb频繁的页分裂,使得insert的操作十分低效。

2、结合问题定制方案

既然UUID作为主键带有这样那样的问题,难道说让我们在设计表结构时要放弃使用UUID吗?答案是否定的。我们可以通过采用binary(16)数据类型和重新安排UUID的顺序来解决之前提到的两个问题。

首先,BINARY(16) 这个二进制形式数据类型使用16个字节,比人类可读形式(“文本”形式)使用的VARCHAR(36)小的多。注意:只是二进制!没有字符集,没有排序,只有十六个字节。也许在某些应用程序中,文本形式仍然是必需的。那么我们可以使用虚拟列(MySQL5.7的新特性,虚拟列不占用存储空间)来存放文本形式的UUID。

然后,还有如何巧妙地重新排列二进制形式的字节的问题。我们在之前的问题二中已经了解到,MySQL的UUID()使用version1,最左边三个以破折号分隔的组是8字节的时间戳,最左边的第一组是时间戳的低四个字节; 第二组是中间两个字节时间戳,第三组是两个字节的高位时间戳,最左边的第一组变化最快。

因此,在我们存储UUID之前,重新安排UUID,使得快速变化的部分放到最后,例如:

把432a4ec8-3642-11e9-805a-0050568238b5重组为11e9-3642-432a4ec8-805a-0050568238b5

这种结构比起之前的结构更容易被cache缓存,同时存储上会更加连续。

3、方案验证

1)创建两张表

-- 使用原生的uuid作为主键

create table test_uuid (id_binvarchar(36) PRIMARY KEY, name varchar(200)) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

-- 使用重组后的uuid做为主键

create table test_uuid_ordered (id_bin binary(16) PRIMARY KEY, name varchar(200)) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

2)在mysql中创建转换函数uuidtobin

DELIMITER //

CREATE FUNCTION uuidtobin(_uuid varchar(36))

RETURNS BINARY(16)

LANGUAGE SQL  DETERMINISTIC  CONTAINS SQL  SQL SECURITY INVOKER

RETURN

UNHEX(CONCAT(

SUBSTR(_uuid, 15, 4),

SUBSTR(_uuid, 10, 4),

SUBSTR(_uuid,  1, 8),

SUBSTR(_uuid, 20, 4),

SUBSTR(_uuid, 25) ));

//

DELIMITER ;

3)编写执行测试程序,分别使用uuid()写入数据到test_uuid中和调用函数uuidtobin(UUID())写入数据到test_uuid_ordered中,一次插入1万行数据到相应的表中

6d1ba9c68ba9f39fcbf614d41be8efea.png

4)测试结果

数据尺寸

横轴-插入次数 x 10,000

纵轴-数据文件尺寸(单位MB)

03a1f1105e9236035426fb213a732764.png

UUID表的用varchar(36)存储的文件大小几乎比有序UUID表用BINARY(16)存储的文件大45%

实际处理时间

横轴-插入次数 x 10,000

纵轴-实际时间(单位 秒)

d6627e8180be002cfaa8309871b5788b.png

5)最后添加虚拟列id_text存放“未重新排列”的顺序的UUID文本,可以方便将文本格式用于一些错误日志记录,调试等。

alter table test_uuid_ordered add

id_text varchar(36) generated always as

(

insert(

insert(

insert(

insert(

hex(

concat(substr(id_bin,5,4),substr(id_bin,3,2),

substr(id_bin,1,2),substr(id_bin,9,8))

),

9,0,'-'),

14,0,'-'),

19,0,'-'),

24,0,'-')

) virtual;

### 如何在 MySQL 中正确存储 UUID 数据类型的最佳实践 #### 使用 BINARY(16) 类型存储 UUID 为了高效地存储 UUID,在 MySQL 中推荐使用 `BINARY(16)` 类型而不是传统的 `VARCHAR(36)`。这样做不仅减少了所需的存储空间,还提高了索引性能[^3]。 ```sql CREATE TABLE example ( id BINARY(16) NOT NULL, name VARCHAR(255), PRIMARY KEY (id) ); ``` #### 插入 UUID 值 当向表中插入新记录时,可以通过将字符串形式的 UUID 转换为二进制格式来保存: ```sql INSERT INTO example (id, name) VALUES (UNHEX(REPLACE(UUID(), '-', '')), 'Test Name'); ``` 这段 SQL 语句首先调用 `UUID()` 函数生成一个新的 UUID 字符串,接着移除其中的连字符 `-` 并将其转换成十六进制表示法以便能够存入 `BINARY(16)` 列中。 #### 查询并显示 UUID 如果需要查询带有 UUID 的记录并将它们以标准格式展示给用户,则可以在 SELECT 语句中执行相反的操作—即将二进制数据重新编码回原始的字符串格式: ```sql SELECT HEX(id), name FROM example; ``` 这将会把 `BINARY(16)` 形式的 ID 显示为大写的十六进制字符串,方便阅读和调试. #### 创建虚拟列用于文本形式的 UUID 对于那些既希望保持高效的内部存储又想要提供易于理解的人类友好版本的应用程序而言,可以利用 MySQL 5.7 及以上版本支持的虚拟列功能。这样做的好处是不会额外增加实际磁盘上的开销,因为虚拟列并不真正占用物理空间: ```sql ALTER TABLE example ADD COLUMN uuid_str AS (LOWER(CONCAT( SUBSTR(HEX(id), 1, 8), '-', SUBSTR(HEX(id), 9, 4), '-', SUBSTR(HEX(id), 13, 4), '-', SUBSTR(HEX(id), 17, 4), '-', SUBSTR(HEX(id), 21)))) STORED; ``` 上述命令会在现有表格上添加一个名为 `uuid_str` 的自动生成字段,它总是与对应的 `id` 同步更新,并按照常见的 "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx" 模式呈现出来.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值