MySQL中使用UUID做主键时需要注意的两个坑(译文)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 在InnoDB中使用UUID作为主键需要考虑两个问题


01

小心两个坑


在InnoDB中使用UUID作为主键需要考虑两个问题:

  1. UUID的返回值通常是随机的,而InnoDB的表实质是以主键组织存储的索引,插入新的记录会造成表的再平衡。
  2. 主键包含在每个二级索引中,过长的主键会浪费磁盘和内存的空间。

   让我们看看这个例子:


MySQL > CREATE TABLE my_table (         uuid VARCHAR(36) DEFAULT (UUID()) PRIMARY KEY,         name VARCHAR(20), beers int unsigned);...
MySQL > SELECT * FROM my_table;+--------------------------------------+---------+-------+| uuid                                 | name    | beers |+--------------------------------------+---------+-------+| 17cd1188-1fa0-11ed-ba36-c8cb9e32df8e | Kenny   |     0 || 17cd12e2-1fa0-11ed-ba36-c8cb9e32df8e | lefred  |     1 || 478368a0-1fa0-11ed-ba36-c8cb9e32df8e | Scott   |     1 || 47836a77-1fa0-11ed-ba36-c8cb9e32df8e | Lenka   |     0 |+--------------------------------------+---------+-------+

现在,让我们插入2个新记录:



MySQL > INSERT INTO my_table (name, beers) VALUES ("Luis",1), ("Miguel",5);

我们查看一下这个表的内容:



MySQL > SELECT * FROM my_table;+--------------------------------------+---------+-------+| uuid                                 | name    | beers |+--------------------------------------+---------+-------+| 17cd1188-1fa0-11ed-ba36-c8cb9e32df8e | Kenny   |     0 || 17cd12e2-1fa0-11ed-ba36-c8cb9e32df8e | lefred  |     1 || 36f1ce9a-1fa1-11ed-ba36-c8cb9e32df8e | Luis    |     1 |  <--| 36f1d158-1fa1-11ed-ba36-c8cb9e32df8e | Miguel  |     5 |  <--| 478368a0-1fa0-11ed-ba36-c8cb9e32df8e | Scott   |     1 || 47836a77-1fa0-11ed-ba36-c8cb9e32df8e | Lenka   |     0 |+--------------------------------------+---------+-------+


我们可以看到,两个新记录不是插入表格的末尾,而是插入到中间。InnoDB必须移动两个旧记录才能插入它们之前的两个新记录。在这个小表上(所有记录都在同一个页面上),不会造成任何问题,但设想一下,如果这个表是1TB大!

此外,如果我们使用VARCHCAR数据类型保存UUID,每个字段可能需要146字节(一些utf8字符最多可以占用4个字节+标记VARCHAR结束的2个字节):



MySQL > EXPLAIN SELECT * FROM my_table WHERE         uuid='36f1d158-1fa1-11ed-ba36-c8cb9e32df8e'\G *************************** 1. row ***************************           id: 1  select_type: SIMPLE        table: my_table   partitions: NULL         type: constpossible_keys: PRIMARY          key: PRIMARY      key_len: 146        <--          ref: const         rows: 1     filtered: 100        Extra: NULL

02

解决方案


MySQL用户可以遵循一些最佳实践来避免这两个问题:

  1. 使用数据类型BINARY(16)来存储UUID占用的空间比较小。
  2. 使用函数UUID_TO_BIN(..., swap_flag)对UUID进行转换,这里将swap_flag设置为“1”可以让生成的UUID单向正增长,这里的时间戳的低部分和高部分(分别为十六进制数字的第一组和第三组)被交换,参见:https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_uuid-to-bin

不断地重复执行下面的语句,看到得到结果不一定是正向增长的:


mysql> select hex(uuid_to_bin(uuid()));+----------------------------------+| hex(uuid_to_bin(uuid()))         |+----------------------------------+| 8EC0DA428BF211EDB473001631F55036 |+----------------------------------+1 row in set (0.00 sec)
mysql> select hex(uuid_to_bin(uuid()));+----------------------------------+| hex(uuid_to_bin(uuid()))         |+----------------------------------+| 906E8F628BF211EDB473001631F55036 |+----------------------------------+1 row in set (0.00 sec)

如果加上swap_flag设置为“1”,看到得到结果一直是正向增长的:








mysql> select hex(uuid_to_bin(uuid(),1));+----------------------------------+| hex(uuid_to_bin(uuid(),1))       |+----------------------------------+| 11ED8BF2A04370C4B473001631F55036 |+----------------------------------+1 row in set (0.00 sec)
mysql> select hex(uuid_to_bin(uuid(),1));+----------------------------------+| hex(uuid_to_bin(uuid(),1))       |+----------------------------------+| 11ED8BF2A1931D65B473001631F55036 |+----------------------------------+1 row in set (0.00 sec)

让我们通过下面的例子来了解如何实现:



MySQL > CREATE TABLE my_table2 (            uuid BINARY(16) DEFAULT (UUID_TO_BIN(UUID(), 1)) PRIMARY KEY,            name VARCHAR(20), beers int unsigned);
MySQL > SELECT * FROM my_table2;+------------------------------------+--------+-------+| uuid                               | name   | beers |+------------------------------------+--------+-------+| 0x11ED1F9F633ECB6CBA36C8CB9E32DF8E | Kenny  |     0 || 0x11ED1F9F633ECD6FBA36C8CB9E32DF8E | lefred |     1 |+------------------------------------+--------+-------+

表里面存储的UUID是二进制的,我们使用BIN_TO_UUID对其进行解码:





MySQL > SELECT BIN_TO_UUID(uuid,1), name, beers FROM my_table2;+--------------------------------------+--------+-------+| BIN_TO_UUID(uuid,1)                  | name   | beers |+--------------------------------------+--------+-------+| 633ecb6c-1f9f-11ed-ba36-c8cb9e32df8e | Kenny  |     0 || 633ecd6f-1f9f-11ed-ba36-c8cb9e32df8e | lefred |     1 |+--------------------------------------+--------+-------+

注意BIN_TO_UUID的swap_flag设置为“1”并不是为了让结果看起来单向增长,而是为了把UUID_TO_BIN函数对时间的交换改成正确的。

现在我们可以验证,当我们添加新条目时,它们会添加到表格的末尾:





MySQL > INSERT INTO my_table2 (name, beers) VALUES ("Scott",1), ("Lenka",5); 
MySQL > SELECT * FROM my_table2;+------------------------------------+---------+-------+| uuid                               | name    | beers |+------------------------------------+---------+-------+| 0x11ED1F9F633ECB6CBA36C8CB9E32DF8E | Kenny   |     0 || 0x11ED1F9F633ECD6FBA36C8CB9E32DF8E | lefred  |     1 || 0x11ED1FA537C57361BA36C8CB9E32DF8E | Scott   |     1 |  <--| 0x11ED1FA537C5752DBA36C8CB9E32DF8E | Lenka   |     5 |  <--+------------------------------------+---------+-------+

我们可以把swap_flag设置为“1”进行解码UUID:




MySQL > SELECT BIN_TO_UUID(uuid,1), name, beers FROM my_table2;+--------------------------------------+---------+-------+| BIN_TO_UUID(uuid,1)                  | name    | beers |+--------------------------------------+---------+-------+| 633ecb6c-1f9f-11ed-ba36-c8cb9e32df8e | Kenny   |     0 || 633ecd6f-1f9f-11ed-ba36-c8cb9e32df8e | lefred  |     1 || 37c57361-1fa5-11ed-ba36-c8cb9e32df8e | Scott   |     1 |  <--| 37c5752d-1fa5-11ed-ba36-c8cb9e32df8e | Lenka   |     5 |  <--+--------------------------------------+---------+-------+

当然,现在主键的大小更小,固定为16字节:






MySQL > EXPLAIN SELECT * FROM my_table2         WHERE uuid=UUID_TO_BIN("37c5752d-1fa5-11ed-ba36-c8cb9e32df8e",1)\G *************************** 1. row ***************************           id: 1  select_type: SIMPLE        table: my_table2   partitions: NULL         type: constpossible_keys: PRIMARY          key: PRIMARY      key_len: 16        <---          ref: const         rows: 1     filtered: 100        Extra: NULL

只有这16字节被添加到所有的二级索引中,这样二级索引对长度也变小了。

03

UUID 的版本


目前有两个流行UUID版本,分别是v1和v4。UUID v1的说明参见:https://www.rfc-editor.org/rfc/rfc4122.html

  • UUID v1:是一个通用的唯一标识符,使用时间戳和生成它的计算机的MAC地址生成。
  • UUID v4:是一个使用随机数生成的通用唯一标识符。

使用UUID v4,无法生成任何顺序输出,因此不推荐使用UUID v4做为InnoDB表的主键。

04

总结


总之,如果想在MySQL中使用UUID,请遵循以下建议:

  • 使用UUID v1,而不是UUID v4。
  • 使用BINARY(16)来存储UUID存储UUID。
  • 使用函数UUID_TO_BIN(..., swap_flag)对UUID进行转换,这里将swap_flag设置为“1”。


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
6月前
|
关系型数据库 MySQL
MYSQL:约束(主键约束)
MYSQL:约束(主键约束)
|
2月前
|
存储 SQL 关系型数据库
mysql中主键索引和联合索引的原理与区别
本文详细介绍了MySQL中的主键索引和联合索引原理及其区别。主键索引按主键值排序,叶节点仅存储数据区,而索引页则存储索引和指向数据域的指针。联合索引由多个字段组成,遵循最左前缀原则,可提高查询效率。文章还探讨了索引扫描原理、索引失效情况及设计原则,并对比了InnoDB与MyISAM存储引擎中聚簇索引和非聚簇索引的特点。对于优化MySQL性能具有参考价值。
|
1月前
|
SQL 关系型数据库 MySQL
mysql编写sql脚本:要求表没有主键,但是想查询没有相同值的时候才进行插入
mysql编写sql脚本:要求表没有主键,但是想查询没有相同值的时候才进行插入
35 0
|
3月前
|
存储 关系型数据库 MySQL
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
覆盖索引、前缀索引、索引下推、SQL优化、EXISTS 和 IN 的区分、建议COUNT(*)或COUNT(1)、建议SELECT(字段)而不是SELECT(*)、LIMIT 1 对优化的影响、多使用COMMIT、主键设计、自增主键的缺点、淘宝订单号的主键设计、MySQL 8.0改造UUID为有序
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
|
6月前
|
SQL Java 数据库连接
2万字实操案例之在Springboot框架下基于注解用Mybatis开发实现基础操作MySQL之预编译SQL主键返回增删改查
2万字实操案例之在Springboot框架下基于注解用Mybatis开发实现基础操作MySQL之预编译SQL主键返回增删改查
87 2
|
6月前
|
关系型数据库 MySQL 数据库
MySQL 8.0 新特性之不可见主键
【6月更文挑战第9天】MySQL 8.0 引入了不可见主键特性,提供更灵活的数据库管理方式。不可见主键能减少业务逻辑干扰,提高数据安全性和隐私,同时在某些场景下更适用。示例展示了如何创建和使用不可见主键,但需要注意它可能带来的理解和调试难题。此特性增加了设计和管理数据库的选项,适用于对数据隐私有高要求的场景。随着技术发展,不断学习和探索新特性将提升数据库性能和功能。
91 9
|
6月前
|
存储 SQL 关系型数据库
MySQL数据库——SQL优化(1/3)-介绍、插入数据、主键优化
MySQL数据库——SQL优化(1/3)-介绍、插入数据、主键优化
294 1
|
7月前
|
存储 关系型数据库 MySQL
在MySQL中, 自增主键和UUID作为主键有什么区别?
自增主键和UUID在MySQL中各有优缺点,选择哪种方式作为主键取决于具体的应用场景和需求。例如,在需要高性能插入和查询的场景下,自增主键可能更合适;而在需要保证主键全局唯一性和不可预测性的场景下,UUID可能更合适。
100 0
|
7月前
|
存储 关系型数据库 MySQL
MySQL中, 自增主键和UUID作为主键有什么区别?
MySQL中, 自增主键和UUID作为主键有什么区别?
91 0
|
13天前
|
关系型数据库 MySQL 数据库
Python处理数据库:MySQL与SQLite详解 | python小知识
本文详细介绍了如何使用Python操作MySQL和SQLite数据库,包括安装必要的库、连接数据库、执行增删改查等基本操作,适合初学者快速上手。
88 15