通用唯一识别码UUID标准和应用实例
在软件开发、数据库管理和分布式系统中,唯一标识符(Unique Identifier)是一个至关重要的概念。我们常常会为实体设计唯一的ID,以保证其在系统中的唯一性,避免实体冲突。自增ID、UUID等唯一标识符便在这样的需求下应运而生。
什么是UUID?
UUID(Universally Unique Identifier, 通用唯一识别码)由RFC 4122定义,技术上等同于 ITU-T Rec. X.667 | ISO/IEC 9834-8,最早由开放软件基金会标准化。
标准格式
UUID 的 16 个 8 位字节表示为 32 个十六进制数字,由连字符 '-' 分隔成五组显示,形式为“8-4-4-4-12”总共 36 个字符(32 个十六进制数字和 4 个连字符)。
- UUID要求以小写形式生成字符,同时对输入不区分大小写。
形如:xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
名称 | 字节 | 字长(16进制数字码长) | 说明 |
---|---|---|---|
time_low | 4 | 8 | 整数:低位 32 bits 时间戳 |
time_mid | 2 | 4 | 整数:中间位 16 bits 时间戳 |
time_hi_and_version | 2 | 4 | 最高有效位中的 4 bits“版本”(Mxxx ),后面是高 12 bits 的时间戳 |
clock_seq_hi_and_res clock_seq_low | 2 | 4 | 最高有效位为 1-3 bits“变体”(Nxxx ),后跟13-15 bits 时钟序列 |
node | 6 | 12 | 48 bits 节点 ID |
我们重点关注其中的版本和变体。
变体 variant
变体(variant)字段占位1~3bit,RFC 4122共规定了4种变体。(x
代表位置没有意义)
- 变体 0 (形如
0xxx
), 用于向后兼容已经过时的1988年开发的 Apollo 网络计算系统(NCS)1.5 UUID 格式; - 变体 1 (形如
10xx
), 按照大端序作为二进制存储与传输,RFC称“保留,微软公司向后兼容; - 变体 2 (形如
110x
), 按照小端序作为二进制存储与传输; - 变体 3 (形如
111x
), 保留未使用。
在RFC 4122中,我们实际关注的是变体1(10xx
)这一类别,所以我们在变体(Nxxx
)这一字段事实上只能见到四个值:
1000 - 8
1001 - 9
1010 - a
1011 - b
版本 version
版本可以理解为变体下不同的子类型,RFC 4122中定义了五个版本,版本(Mxxx
)取值为1/2/3/4/5.
版本1 日期时间和MAC地址
- 时间戳版本,60-bit 的时间戳和节点的48-bit MAC地址而生成的;
- 优点:基于时间戳和MAC地址生成,保证了UUID唯一性;
- 缺点:有暴露节点MAC隐私信息的风险
版本2 日期时间和DCE标识符
- 在版本1的基础上使用"DCE安全标识符"
- 优点:替换掉了MAC地址,解决了暴露计算机隐私信息的问题;
- 缺点:DCE实现在RFC 4122中未提及,标识符对生成速率有影响
版本3/5 散列命名空间
- 通过将命名空间(例如域名)和一个名字组合并生成哈希值来创建UUID。任何所需的UUID都可以用作命名空间指示符。
- 版本3 - MD5
- 版本5 - SHA1
版本4 随机
- 一个随机或伪随机生成的60位值。RFC 4122 建议“在各种主机上生成 UUID 的分布式应用程序必须愿意依赖所有主机上的随机数源。如果这不可行,则应使用名称空间变体。”
应用场景
- 数据库键值:UUID 通常用作数据库表中的唯一键,MySQL、SQL Server、PostageSQL等DBMS都提供了不同的UUID函数;
- 分布式系统:在没有中央协调器的情况下,确保唯一标识符;
- 软件构建:在构建过程中生成唯一的组件或版本标识符;
- 文件系统:用于标识文件或目录的唯一性。
一些问题
- 大小问题:相对于简单的ID标识符,UUID虽然提供了极低的唯一性,但也因其128位的长度占用了相对较大的空间;
- 如数据库,使用UUID作为主键会导致索引体积增大;
- 可排序性问题:UUID的生成方式(尤其是版本4随机生成)导致它们在时间上是无序的;
- 数据库索引:无序的UUID会导致数据库索引碎片化,进而降低查询性能;
- 日志记录:无序的UUID可能导致难以按时间顺序排序或筛选记录;
- 可预测性问题:某些版本的UUID(如版本1)可能会泄露生成时间和生成设备的信息,从而存在安全隐患。
- 重复问题:尽管UUID设计上是唯一的,但并不能完全排除重复的可能性;
- 生成算法设计不合理;
- 依赖MAC、ID等选取不合理;
- 随机数碰撞;
一些解决问题的方案
- 使用
BINARY(16)
而不是CHAR(36)
来存储UUID; - 使用有序UUID(如时间UUID)来确保在时间上的排序性,从而提高索引和查询性能;
- 对于需要短而可读标识符的场景,可以考虑使用短UUID;
UUID 应用实现
Python
import uuid
# 生成一个UUID(版本4)
unique_id = uuid.uuid4()
print(unique_id)
MySQL
方案1:调用UUID()
函数:
INSERT INTO my_table (id, name)
VALUES (UUID(), 'Example Name');
方案2:触发器实现
-- 这个触发器会在每次插入数据之前检查`id`是否为空,如果是,则生成一个新的UUID。
DELIMITER //
CREATE TRIGGER before_insert_my_table
BEFORE INSERT ON my_table
FOR EACH ROW
BEGIN
IF NEW.id IS NULL THEN
SET NEW.id = UUID();
END IF;
END;
//
DELIMITER ;
-- 调用
INSERT INTO my_table (name) VALUES ('Example Name');
更多方案
正如我们一开始提到的,UUID事实上要解决的是我们在分布式系统、软件开发和数据库设计等场景下的唯一标识问题。不同的场景下,我们应选取不同的标识符方案,并不一定要因为使用UUID而妥协,标识符的规范应当视场景而定。
由此,我们还有很多针对不同场景下的ID方案:
- 自增ID;
- 一定精度的时间戳;
- 有序UUID;
- NanoID;
- Snowflake 雪花算法;
参考阅读
- RFC 4122 - A Universally Unique IDentifier (UUID) URN Namespace (ietf.org)
- Universally Unique Identifiers (UUIDs) (itu.int)
- 通用唯一识别码 - 维基百科,自由的百科全书 (wikipedia.org)
- 深度解读UUID:结构、原理以及生成机制 - -云- - 博客园 (cnblogs.com)
- 关于UUID的重复性_uuid会重复吗?-CSDN博客
- nanoid - npm (npmjs.com)
- 一文读懂“Snowflake(雪花)”算法-腾讯云开发者社区-腾讯云 (tencent.com)
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 光溯星河
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果