UUID vs GUID
UUID通用唯一识别码(英语:Universally Unique Identifier,缩写:UUID)是用于计算机体系中以识别信息的一个128位标识符。其按照标准方法生成时,在实际应用中具有唯一性,且不依赖中央机构的注册和分配。UUID重复的概率接近零,可以忽略不计。可以在无需任何中心协调的情况下独立生成,这使其成为分布式系统、数据库和 API 的理想选择。
因此所有人都可以自行建立和使用UUID,而且几乎可以确定其不会与既有的标识符重复。也因为如此,在不同地方产生的UUID可以使用于同一个数据库或同一个频道中,而且几乎不可能重复。其应用相当普遍,许多计算平台都提供了对于生成和解析UUID的支持。
1990年代,UUID 原本是用于阿波罗电脑的网络计算系统,后被用于开放软件基金会的分布式运算环境。分布式运算环境UUID的初始设计基于网络计算系统UUID,其设计受 Domain/OS 中定义和使用的(64位)唯一标识符的启发,这是一个也由阿波罗电脑设计的操作系统。后来,Microsoft Windows平台采用分布式运算环境设计作为全局唯一标识符(GUID)。2005年7月,RFC 4122 为 UUID 注册了一个 URN 命名空间,并制定了早期的规范。当 RFC 4122 作为互联网工程任务组标准发布时,国际电信联盟基于先前的标准和 RFC 4122 早期版本标准化了 UUID。
由开放软件基金会标准化,作为分布式运算环境(DCE)的一部分。UUID 被纪录为 ISO/IEC 11578:1996 "Information technology – Open Systems Interconnection – Remote Procedure Call(RPC)" 和后来的 ITU-T Rec. X.667 | ISO / IEC 9834-8:2005 规范的一部分。
互联网工程任务组公布了标准 RFC 4122,技术上等同于 ITU-T Rec. X.667 | ISO/IEC 9834-8。
格式
在其规范的文本表示中,UUID 的 16 个 8 位字节表示为 32 个十六进制数字,由连字符 '-' 分隔成五组显示,形式为“8-4-4-4-12”总共 36 个字符(32 个十六进制数字和 4 个连字符)。因此每一位十六进制数对应 4 个比特(bit);一个完整的 UUID 有 32 位十六进制数,总计 128 比特。例如:
123e4567-e89b-12d3-a456-426655440000
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
UUID 由 RFC 9562(前身 RFC 4122)标准化,其标准字符串表示形式为 32 个十六进制数字,分为 5 组,并用连字符分隔:xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
其中:M 表示 UUID 的版本(Version)N 表示 UUID 的变体(Variant)。
四位数字 M表示 UUID 版本,数字 N的一至三个最高有效位表示 UUID 变体。在例子中,M 是 1 而且 N 是 a(10xx2),这意味着此 UUID 是“变体1”、“版本1”UUID;即基于时间的 DCE/RFC 4122 UUID。
规范的 `8-4-4-4-12` 格式字符串基于 UUID 的16个字节的“记录布局”,因此其记录结构如下:
| 名称 | 长度 (字节) | 长度(16进制数字码长) | 说明 |
| time_low | 4 | 8 | 整数:低位 32 bits 时间 |
| time_mid | 2 | 4 | 整数:中间位 16 bits 时间 |
| time_hi_and_version | 2 | 4 | 最高有效位中的 4 bits“版本”,后面是高 12 bits 的时间 |
| clock_seq_hi_and_res clock_seq_low | 2 | 4 | 最高有效位为 1-3 bits“变体”,后跟13-15 bits 时钟序列 |
| node | 6 | 12 | 48 bits 节点 ID |
这些字段对应于“版本1”和“版本2”(基于时间的)UUID中的字段,但是“8-4-4-4-12”的表示适用于所有UUID,即使对于生成方式不同的UUID也是如此。
RFC 4122 第 3 节要求以小写形式生成字符,同时对输入不区分大小写,尽管一些常用的实现违反了此规则。
Microsoft GUID有时会以大括号表示:
{123e4567-e89b-12d3-a456-426655440000}
不应将此格式与“Windows注册表格式”相混淆,后者指的是大括号内的格式。
RFC 4122为UUID定义了统一资源名称(URN)命名空间。作为URN呈现的UUID如下:
urn:uuid:123e4567-e89b-12d3-a456-426655440000
变体
UUID的变体(variant)字段,占1到3比特。RFC 4122定义了4种变体:
1).变体 0 (最显著位为0,二进制为0xxx2,十六进制表示为016到716) 用于向后兼容已经过时的1988年开发的 Apollo 网络计算系统(NCS)1.5 UUID 格式。 前6字节是48比特时间戳(从1980年1月1日UTC开始的4微秒的滴答数),随后2个字节保留,再1个字节是地址族(address family,使用了0..13个情形),最后7个字节是主机ID。这类似于UUID的版本1.
2).变体 1 (二进制为10xx2,十六进制为816到b16),定义在RFC 4122/DCE 1.1 UUIDs, 或"Leach–Salz" UUID。它是按照大端序作为二进制存储与传输。
3).变体 2 (二进制为110x2,十六进制为c16或d16),RFC称“保留,微软公司向后兼容”。早期的Microsoft Windows平台的GUID使用该格式。和完全使用大端序的变体 1 不同,变体 2 其中一部分按照小端序作为二进制存储与传输。
4).形如111x2保留未使用。
目前的UUID规范使用变体1和2。在文字表示上,两种变体只有代表变体的比特不同。上面的字段也定义了两种变体之间的字节转换。前三个字段是无正负号的32和16位数字,需要进行转换,而后两个字段是由未解释的字节组成,不需要进行转换。这种转换同样适用于版本3、4和5,其中的规范字段与UUID的内容无关。
虽然一些重要的GUID名义上是变体2的UUID,例如组件对象模型(Component Object Model)IUnknown界面的标识符,但是在微软Windows软件中所生成和使用的、被称为“GUID”的许多标识符是使用标准的变体1的RFC 4122/DCE 1.1大端序UUID。目前 Microsoft guidgen工具软件产生变体1的结果。某些微软文档称GUID与UUID是同义词,如同RFC 4122中表示UUID“也被称作GUID”。这些文件表明了虽然“GUID”最初指代微软所使用的其中一种UUID变体,但现在已经成为UUID的另一个名称,含括变体1和2。
对于“变体(variants)1”和“变体2”,标准中定义了五个版本(versions),并且在特定用例中某些版本可能比其他版本更合适。版本由字符串中的 M 指示。
版本1的UUID是根据时间和节点ID(通常是MAC地址)生成;版本2的UUID是根据标识符(通常是组或用户ID)、时间和节点ID生成;版本3、版本5透过对命名空间(namespace)标识符和名称进行散列生成确定性的UUID;版本4的UUID则使用随机性或伪随机性生成。
Nil UUID
这是一个特例,值为 00000000-0000-0000-0000-000000000000 ;也就是说所有位都设置为 0。它被保证不是唯一的。
通常用作 UUID 格式的模板,或者在你需要一个易于识别且明确不唯一的 UUID 时使用。
Max UUID(最大 UUID)
这是一种特殊的 UUID,其定义为 128 个比特位全部置为 1。可以将其视为Nil UUID(全零 UUID)的“反向”形式:
FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF
在某些场景下,当系统需要一个 128 位的 UUID,但又想表达诸如 “UUID 列表结束” 这样的概念时,就可以将 Max UUID 用作一个哨兵值(sentinel value)。它被预留用于此类特定于实现的需求。
版本1(日期时间和MAC地址)
版本1的UUID,是根据 60-bit 的时间戳和节点(生成UUID的计算机)的48-bit MAC地址而生成的。

将时间戳、时钟序列(Clock Sequence)和节点 ID(通常是 MAC 地址)组合而成。当你需要知道一个 UUID 的创建时间时,它非常有用。
版本2(日期时间和MAC地址,DCE安全版本)
RFC 4122 保留了版本2的UUID用于“DCE security”;但并没有提供任何细节。因此许多 UUID 实现省略了“版本2”。但“版本2”UUID 的规范由 DCE 1.1 身份验证和安全服务规范提供。
版本3和版本5(基于命名空间名称)
“版本3”和“版本5”的 UUID 透过散列(hashing)命名空间标识符和名称生成。版本3使用 MD5 作为散列算法,版本5则使用 SHA1。

名称空间标识符本身就是一个 UUID。该规范提供了 UUID 用来表示命名空间为了统一资源定位符(URLs),完整域名、对象标识符和 X.500;但任何所需的UUID都可以用作命名空间指示符。
它俩有一个特性:相同名称空间和名称将映射到同一个UUID;然而,即使已知其中一项,也无法透过暴力搜索之外的方法从UUID逆向推导出另外一项。RFC 4122 推荐使用版本5(SHA1)而不是版本3(MD5),并建议不要使用任一版本的 UUID 作为安全凭证。
规范预定义了 4 个标准命名空间:
DNS: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
URL: 6ba7b811-9dad-11d1-80b4-00c04fd430c8
OID: 6ba7b812-9dad-11d1-80b4-00c04fd430c8
X.500 DN: 6ba7b814-9dad-11d1-80b4-00c04fd430c8
当在 v3 或 v5 UUID 生成器中选择一个预定义命名空间时,系统会自动将其转换为对应的 UUID。

最佳用途:需要 MD5 型 UUID 的遗留系统、向后兼容。
注意:使用 MD5 算法(虽然不适用于加密安全场景,但对于 UUID 生成本身是足够的)。
版本4(随机)
版本4的UUID是随机生成的。与其他 UUID 一样,其中4位用于代表“版本4”,2到3比特代表变体号(102 或 1102 分别用于变体 1 和 2)。因此对于变体1(即大多数 UUID),随机生成的版本4的UUID会保留6比特用于表示变体号和版本号,其余122比特用于随机生成,版本4变体2的UUID(传统GUID)则为变体1的一半,因为可用的随机位少一个,变量消耗 3 比特。

在 Microsoft 系统上,v4, variant-2 被称为 “GUID”。GUID 是微软对 DCE UUID 的一种实现,基本遵循 RFC 4122 标准,唯一的区别在于字节序(endianness)。
最佳用途:绝大多数应用、数据库、API、会话令牌(Session Token)。
唯一性保障:2^122 个可能的取值。
版本6(重排序的时间戳型)
v6 是 v1 的一个重新排序版本,它调整了时间戳部分的比特位顺序,使其具备天然的可排序性。它使用与 v1 相同的 60 位格里高利历时间戳,但将最高有效位放在前面。

最佳用途:当需要可排序性时,作为 v1 的直接替代方案。
优势:在保持与 v1 兼容的同时,实现了字典序(Lexicographical)排序。
版本7(Unix 时间戳,可排序型)
这是 RFC 9562 中定义的最新版本。它将 Unix 时间戳(以毫秒为单位)编码到 UUID 中,使得 v7 UUID 能够按创建时间自然排序。

最佳用途:数据库主键、按时间排序的事件、现代分布式系统。
优势:兼具可排序性和随机性,相比 v1 更适合现代数据库的需求。
版本8
UUIDv8 为实验性或厂商特定的使用场景提供了一种格式。其唯一强制要求是:变体(variant)必须按照RFC规定进行设置。

其唯一性完全取决于具体实现,使用者绝不能对其唯一性做出任何假设。以下是一些可能使用 UUIDv8 的典型场景:
某个实现希望在 UUID 中嵌入本文档未定义的额外信息。
某个实现受到特定应用程序或编程语言的限制,无法使用现有的任何一种标准 UUID。
小结一下变体(Variant)与版本(Version)
版本(Version)
| 十六进制数字 (M) | UUID 版本 |
| 0 | Nil UUID |
| 1 | version-1 基于格里高利历时间戳的 UUID |
| 2 | version-2 保留,用于 DCE 安全版本(内嵌 POSIX UUID) |
| 3 | version-3 基于名称的 UUID,使用 MD5 哈希算法 |
| 4 | version-4 随机或伪随机生成的 UUID |
| 5 | version-5 基于名称的 UUID,使用 SHA-1 哈希算法 |
| 6 | version-6 重排序版基于格里高利历时间戳的 UUID |
| 7 | version-7 基于 Unix 时间戳(毫秒)的 UUID |
| 8 | version-8 用于本文档中定义的自定义 UUID 格式 |
| 9 - e | 未知版本 |
| f | Max UUID |
UUID 的变体(Variant)
要确定变体,需要分析 UUID 第 17 位(“N”)所对应的 4 个二进制位。
关键在于这 4 位二进制数的开头模式:
如果以 10 开头,则变体为 “DCE 1.1, ISO/IEC 11578:1996”(这是最常见的标准变体);
如果以 110 开头,则该 UUID 是一个 “Microsoft GUID”。
下表给出了完整的变体对照关系:
| 二进制表示 | 十六进制数字 (N) | 变体 (Variant) |
| 0xxx | 0 - 7 | 保留(用于兼容旧的 NCS 标准) |
| 10xx | 8 - b | DCE 1.1, ISO/IEC 11578:1996(标准 UUID) |
| 110x | c - d | 保留(Microsoft GUID) |
| 1110 | e | 保留(供将来使用) |
| 1111 | f | 未知 / 无效(必须以 0 结尾) |
常见的 UUID 格式
| 格式 | 示例 | 用途 |
| 标准格式 | 550e8400-e29b-41d4-a716-446655440000 | 最常用 |
| 花括号格式 | {550e8400-e29b-41d4-a716-446655440000} | Windows 注册表 |
| URN 格式 | urn:uuid:550e8400-e29b-41d4-a716-446655440000 | 符合 RFC 4122 的统一资源名称(URN) |
| 无连字符格式 | 550e8400e29b41d4a716446655440000 | 紧凑存储(节省空间) |
应用
1、文件系统
重要用途包括 ext2/ext3/ext4 文件系统用户空间工具(e2fsprogs 使用 util-linux 提供的 libuuid)、LUKS 加密分区、GNOME、KDE 和 Mac OS X,其中大部分源自 曹子德(Theodore Ts'o)的实现。
分区标签和分区UUID都存储于超区块中。两者皆为文件系统的一部分,而不是分区的一部分。例如,ext2~4包含UUID,而NTFS或FAT32则没有。
超区块是文件系统的一部分,因此被完全包含在分区中,因此如果你执行dd if=/dev/sda1 of=/dev/sdb1,sda1和sdb1都会拥有相同的标签和UUID。
2、COM
Microsoft的组件对象模型(COM)中使用了几种GUID :
IID - 接口标识符;(在系统上注册的接口标识符存储在Windows注册表中的[HKEY_CLASSES_ROOT\Interface])
CLSID - 类标识符;(存储在[HKEY_CLASSES_ROOT\CLSID])
LIBID - 类型库标识符;(存储于[HKEY_CLASSES_ROOT\TypeLib])
CATID - 类别标识符;(它在一个类中的存在将其识别为属于某些类别类别,列于[HKEY_CLASSES_ROOT\Component Categories])
3、作为数据库主键
UUID 通常用作数据库表中的唯一键。下文还要开一节继续介绍。
Microsoft SQL Server 版本4 Transact-SQL 中的 NEWID 函数会返回标准随机版本4的UUID,而 NEWSEQUENTIALID 函数返回类似于 UUID 的 128 位标识符,这些 UUID 会依序递增,直到下次系统重启。另一方面,尽管名称如此,但 Oracle Database SYS_GUID 函数不会返回标准 GUID;相反它根据主机标识符和进程或线程标识符返回一个16字节的 128 位 RAW 值,有点类似于 GUID。
PostgreSQL 包含一个 UUID 数据类型,并且可以通过使用模块中的函数生成大多数版本的UUID。
MySQL 提供了一个 UUID 函数,它生成标准的版本1 UUID。
当 UUID 用作主键时,版本3、4和5 UUID 的随机性以及 版本1和2 UUID 内的字段的排序可能会产生数据库定位或性能问题。例如,2002年 Jimmy Nilsson 报告说,当用作主键的版本4 UUID 被修改为包含基于系统时间的非随机后缀时,Microsoft SQL Server的性能显着提高。Nilsson 承认,这种所谓的“COMB”(组合时间和GUID)方法使UUID非标准并且更有可能被复制,但 Nilsson 仅要求在应用程序中的唯一性。透过重新排序和编码版本1和版本2的UUID,将时间戳放在最前面,可以避免插入所造成的性能损失。
诸如Laravel这样的部分网络框架支持“时间戳优先”的UUID,可以将UUID有效存储于索引数据库中。这种UUID是版本4格式的COMB UUID,但其中前48位组成了一个时间戳,就像版本1的UUID一样。其他基于COMB UUID概念的指定格式包括:
1).ULID,其抛弃了用于表示版本4的4位,并且默认使用base32编码。
2).UUID版本6到8是三种COMB UUID格式的正式提议。
GUID
全局唯一标识符(英语:Globally Unique Identifier,缩写:GUID;发音为/ˈɡuːɪd/或/ˈɡwɪd/)是一种由算法生成的唯一标识,通常表示成32个16进制数字(0-9,A-F)组成的字符串,如:{21EC2020-3AEA-1069-A2DD-08002B30309D},它实质上是一个128位长的二进制整数。GUID一词有时也专指微软对UUID标准的实现。
GUID的主要目的是产生完全唯一的数字。在理想情况下,任何计算机和计算机集群都不会生成两个相同的GUID。GUID的总数也足够大,达到了2128(3.4×10^38)个,所以随机生成两个相同GUID的可能性是非常小的,但并不为0。所以用于生成GUID的算法通常都加入了非随机的参数(如时间),以保证这种重复的情况不会发生。
Windows操作系统使用GUID来标识COM对象中的类和界面。一个脚本可以不需知道DLL的位置和名字直接通过GUID来激活其中的类或对象。
英特尔的全局唯一标识分区表使用GUID来标识硬盘和分区。微软的ActiveX使用UUID来标识每一个不同的浏览器控件。
基本结构
GUID本质上是一个16字节(128位)的二进制数,最常见的结构如下:
| 位 | 字节 | 描述 | 字节序 |
| 32 | 4 | 数据1 | 原生 |
| 16 | 2 | 数据2 | 原生 |
| 16 | 2 | 数据3 | 原生 |
| 64 | 8 | 数据4 | 大端序 |
数据4的字节序和GUID显示成文本的结果相同,而其它3个数据在小端序的机器(如英特尔的CPU)上必须先转换成大端序。
数据4的第一个字节的第1-3位表示所使用的GUID变种类型:
| 模式 | 描述 |
| 0 | 向后兼容网络计算系统 |
| 10 | 标准 |
| 110 | 向后兼容微软组件对象模型 |
| 111 | 保留至将来使用 |
数据3最高的4位表示版本号和所使用的算法。
文本编码
GUID通常会写成16进制数的字符串,如:
3F2504E0-4F89-11D3-9A0C-0305E82C3301
这种文本表示包括了如下部分:
| 16进制数的位数 | 描述 |
| 8 | 数据1 |
| 4 | 数据2 |
| 4 | 数据3 |
| 4 | 数据4的最初2字节 |
| 12 | 数据4的剩余6字节 |
上述表示方法通常放在一对大括号里边,如:
{3F2504E0-4F89-11D3-9A0C-0305E82C3301}
当需要使用更少的字符表示GUID时,可能会使用Base64或Ascii85编码。Base64编码的GUID有22-24个字符,如:
7QDBkvCA1+B9K/U0vrQx1A
7QDBkvCA1+B9K/U0vrQx1A==
Ascii85编码后是20个字符,如:
5:$Hj:Pf\4RLB9%kU\Lj
在URN中,GUID第一版的命名空间标识是"uuid",如:
urn:uuid:3F2504E0-4F89-11D3-9A0C-0305E82C3301
两者的区别
如果在Windows生态圈特别是com生态圈经历过,肯定见过 GUID。如果接触过数据库、API 或 Unix 系统,也一定遇到过 UUID。它们看起来一模一样——都是带连字符的 32 位十六进制数。但总在争论该叫哪个名字,它们之间真的有区别吗?
简短回答:几乎没有。但就是这“几乎没有”的部分,在某些特定场景下却至关重要。今天,我们就来彻底厘清这个问题。
1).UUID(Universally Unique Identifier,通用唯一标识符)是 RFC 9562(前身是 RFC 4122)定义的国际标准。它是大多数编程语言、数据库和协议中广泛认可的术语。
2).GUID(Globally Unique Identifier,全局唯一标识符)是 微软对同一概念的称呼。它起源于 COM(组件对象模型)时代,并一直沿用至今,贯穿整个 Windows 和 .NET 生态系统。
它们采用相同的格式,都是 128 位的值,拥有完全一致的带连字符的十六进制字符串表示。在 Linux 上生成的 UUID,在 Windows 上就是一个有效的 GUID,反之亦然。为什么会有两个名字呢?
UUID 的起源
UUID 的概念最早由 Apollo Computer 公司在 20 世纪 80 年代提出,作为其网络计算系统(NCS)的一部分。后来,开放软件基金会(OSF)将其纳入分布式计算环境(DCE)规范。最终,这一规范在 2005 年被正式确立为 RFC 4122,并于 2024 年更新为 RFC 9562。
其目标非常明确:让任何计算机都能独立生成一个标识符,无需与中央机构协调,就能保证其在全球范围内的唯一性。
微软的 GUID
到了 90 年代,微软在开发 COM 和 DCOM 技术时,同样需要为组件接口、类和类型库生成唯一的标识符。他们没有直接采用UUID这个术语,而是将其命名为 GUID(全局唯一标识符)。
其实现基于 DCE UUID 规范,但也带有一些微软特有的行为:
1).Windows 提供了 CoCreateGuid() 和 UuidCreate() 等 API 函数。
2).Visual Studio 在项目文件、接口和 COM 注册中大量使用 GUID。RAD Studio使用了MS Build的构建生态,产生同样GUID依赖。
3)..NET Framework 将 System.Guid 作为一个核心内置类型。
这个品牌名称就此深入人心。几十年过去了,.NET 开发者们依然习惯说 “GUID”,而业界其他地方则普遍使用 “UUID”。
技术差异
字节序(Endianness)
最实质性的技术差异在于字节序——即多字节数据在内存中的存储方式。
UUID (RFC 标准):前三个字段采用大端序(网络字节序)。
time_low: 4 字节,大端序
time_mid: 2 字节,大端序
time_hi_and_version: 2 字节,大端序
clock_seq: 2 字节,大端序
node: 6 字节
Microsoft GUID:采用混合字节序——前三个字段是小端序(x86 原生),后两个字段则是大端序。
Data1: 4 字节,小端序
Data2: 2 字节,小端序
Data3: 2 字节,小端序
Data4: 8 字节,大端序
在实践中意味着什么
当看到字符串形式(如 550e8400-e29b-41d4-a716-446655440000)时,两者是完全一样的。差异只存在于二进制表示中——也就是字节在内存或磁盘上的实际排列顺序。
以 UUID 00112233-4455-6677-8899-aabbccddeeff 为例:
| 格式 | 内存中的字节序列 |
| RFC UUID (大端序) | 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff |
| Microsoft GUID (混合端序) | 33 22 11 00 55 44 77 66 88 99 aa bb cc dd ee ff |
可以看到,前 8 个字节被分组并进行了反转。这种情况可能会在以下场景引发 Bug:
1).在 SQL Server(存储的是 GUID 的二进制)和 Web API(通常处理 UUID 字符串)之间进行二进制转换。
2).在非 Windows 工具中读取 Windows 注册表里的 GUID 二进制数据。
3).在 COM 组件与非微软系统之间进行互操作。
字符串格式完全一致
当序列化为字符串时,GUID 和 UUID 是可以互换的:
550e8400-e29b-41d4-a716-446655440000
两者都使用 32 个十六进制数字,并用 4 个连字符分成 5 组(8-4-4-4-12)。大小写在技术上是不敏感的,但约定俗成有所不同:
UUID 通常使用小写:
550e8400-e29b-41d4-a716-446655440000
GUID在微软语境下常用大写,有时还会加上花括号:
{550E8400-E29B-41D4-A716-446655440000}
在哪里会碰到它们
UUID 领地
| 上下文 | 示例 |
| PostgreSQL | UUID 数据类型 |
| Python | uuid.uuid4() |
| Java | java.util.UUID |
| JavaScript | crypto.randomUUID() |
| RFC 规范 | RFC 9562 |
| Linux/macOS 工具 | uuidgen 命令 |
| REST APIs | 文档中通常称为 "UUID" |
GUID 领地
| 上下文 | 示例 |
| .NET / C# | System.Guid.NewGuid() |
| SQL Server | UNIQUEIDENTIFIER 类型, NEWID() |
| Visual Studio | .sln 项目文件中的项目 GUID |
| COM / DCOM | 接口 ID、类 ID |
| Windows 注册表 | 组件注册信息 |
| PowerShell | [guid]::NewGuid() |
某些场景下这两个术语会被互换使用:
MySQL 文档在解释时会同时提到 “UUID”(函数名)和 “GUID”。
Stack Overflow 上的问题经常将二者视为同义词。
API 文档的用词取决于作者的技术背景。
用作数据库主键的实践
数十年来,自增整数(Auto-increment Integer)一直是关系型数据库的默认主键选择。它简单、高效且被广泛支持。那么,为何越来越多的团队开始转向 UUID 呢?
答案在于现代应用的分布式本质。微服务需要独立生成 ID,移动客户端需要在离线状态下创建记录,数据需要在分片、副本和不同区域之间进行合并。而自增整数依赖一个中心化的权威节点来分配下一个数字——在分布式世界里,这无疑是一个性能瓶颈。
UUID 正是为解决此问题而生,但它也带来了新的权衡。如何规避其性能陷阱,探寻UUID作为数据库主键的最佳方案。
为何要将 UUID 用作主键
1. 分布式生成
任何服务、客户端或设备都可以独立生成 UUID,无需与中心数据库协调。你甚至可以在向数据库插入记录之前就知晓其 ID。
这使得以下场景成为可能:
离线优先(Offline-first)的应用,在设备上创建记录后异步同步。
微服务架构,各服务可自行生成 ID,无需调用数据库。
事件溯源(Event Sourcing),事件在持久化前即可携带自身 ID。
跨系统数据迁移,彻底避免 ID 冲突。
2. 避免信息泄露
自增 ID 会暴露敏感信息:
/api/users/15847 向攻击者暗示系统大约有 15,847 名用户。
顺序 ID 是可枚举的——攻击者可以轻易地遍历 /api/orders/1 到 /api/orders/99999。
而 UUID(如 7c9e6679-7425-40de-944b-e07fc1f90ae7)则完全不透露记录数量、顺序或创建时间(特指 v4 版本)。
3. 便于数据合并
当需要合并来自多个数据库的数据(如分库分表、多租户、公司并购)时,自增 ID 极易发生冲突。而 UUID 则天然无冲突。两个独立的数据库可以各自生成数百万条记录,合并时却毫无压力。
4. API 稳定性
UUID 是绝佳的对外公开标识符。它们不透明、非顺序,且不会因数据库结构调整而改变。最佳实践是:在 API 中暴露 UUID,而将自增整数作为内部实现细节(如果仍需使用的话)。
性能陷阱:随机 UUID 的代价
这里才是关键所在。在基于 B-tree 索引的数据库中,将随机 UUID(v4)用作主键会导致可测量的性能下降。原因如下:
B-Tree 索引的工作原理
大多数数据库(如 PostgreSQL、MySQL InnoDB)将主键索引存储为 B-tree。数据在物理上按主键顺序排列,当插入新行时:
顺序键(自增)新行总是追加到末尾。B-tree的最后一页会常驻内存(“热页”),插入操作快速且可预测。
随机键(UUID v4) 新行会随机插入到树的各个位置,导致:
页分裂(Page splits):满页需要分裂以容纳新数据。
缓存抖动(Cache thrashing):整个索引都需加载到内存,而非仅尾部。
碎片化(Fragmentation):页面利用率低,浪费存储空间。
写入放大(Write amplification):每次插入产生更多磁盘 I/O。
真实世界的影响
对于一张拥有千万级(10M+)数据的表:
使用 UUID v4 作为主键,插入速度可能比自增 ID 慢 2-5 倍。
由于碎片化,索引体积会增大 20%-40%。
范围扫描和分页查询变慢,因为逻辑上相邻的行在物理上是分散的。
终极解决方案:UUID v7
UUID v7 完美解决了上述性能问题。它在 ID 前缀嵌入了时间戳,使得生成的 UUID 具有时间序。新生成的 UUID 总是大于之前的,因此插入操作总是发生在 B-tree 的末尾——与自增整数的表现如出一辙。
实战指南:各数据库实现
PostgreSQL
-- 使用原生 UUID 类型(16字节,高效存储)
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- v4
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT now()
);
-- 对于 UUID v7 (PostgreSQL 17+)
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuidv7(),
user_id UUID REFERENCES users(id),
total DECIMAL(10,2)
);
提示:PostgreSQL 的 UUID 类型在内部以 16 字节存储,远比 36 字符的字符串表示更紧凑。
MySQL (InnoDB)
-- 为获得最佳性能,请存储为 BINARY(16)
CREATE TABLE users (
id BINARY(16) PRIMARY KEY,
-- 虚拟列用于方便地显示为标准格式
id_text VARCHAR(36) GENERATED ALWAYS AS (
INSERT(INSERT(INSERT(INSERT(
HEX(id), 9, 0, '-'), 14, 0, '-'), 19, 0, '-'), 24, 0, '-')
) VIRTUAL,
email VARCHAR(255) UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- MySQL 8.0+ 的 swap 标志可重排时间戳位,提升局部性
INSERT INTO users (id) VALUES (UUID_TO_BIN(UUID(), 1));
重要:在 InnoDB 中,主键就是聚簇索引。使用随机 UUID v4 作为主键会直接导致整个数据表碎片化,而不仅仅是索引。
SQLite
SQLite 没有原生 UUID 类型,可存储为 TEXT 或 BLOB。
CREATE TABLE items (
id TEXT PRIMARY KEY, -- '550e8400-e29b-41d4-...'
name TEXT
);
应用层 vs 数据库生成
在分布式系统中,强烈建议在应用层生成 UUID,而非依赖数据库:
// Node.js
import { v7 as uuidv7 } from 'uuid';
const newUser = { id: uuidv7(), email: 'user@example.com' };
await db.insert('users', newUser); // 插入前已知 ID
# Python
from uuid6 import uuid7
user = User(id=str(uuid7()), email="user@example.com")
session.add(user)
存储优化与混合模式
存储开销对比
| 存储格式 | 单个 ID 大小 | 百万行总大小 |
| INT (自增) | 4 字节 | 3.8 MB |
| BIGINT | 8 字节 | 7.6 MB |
| UUID (二进制) | 16 字节 | 15.3 MB |
| UUID (CHAR 36) | 36 字节 | 34.3 MB |
优化策略:
1).务必以二进制形式存储(PostgreSQL的UUID类型,MySQL的 BINARY(16)),切勿使用 CHAR(36)。
2).混合模式(Hybrid Pattern):内部使用自增整数保证性能,外部使用 UUID 保证分布式友好性。
-- 混合模式示例
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY, -- 内部主键,快速且紧凑
public_id UUID UNIQUE DEFAULT uuidv7(), -- 对外暴露的 ID
email VARCHAR(255) UNIQUE
);
-- 外键关联使用内部整数ID
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id), -- 仅8字节,非16字节
public_id UUID UNIQUE DEFAULT uuidv7()
);
常见误区规避指南
1.将 UUID 存为 VARCHAR(36):这会让所有比较、连接和索引操作处理 36 个字符,而非 16 字节。
2.盲目使用 UUID v4 而忽视性能成本:在小数据量下差异不明显,但在高并发、大数据量场景下,性能差距巨大。
3.在分布式系统中由数据库生成 UUID:这违背了分布式 ID 生成的初衷。
4.忽略对 UUID 外键列的索引:外键列不会自动被索引,务必手动创建。
5.以字符串形式比较 UUID:确保使用数据库的原生 UUID 类型进行比较,以获得最佳性能。
数据库使用UUID的常见问题解答 (FAQ)
Q:新项目该选 UUID 还是自增 ID?
A:如果是单体应用且只有一个数据库,自增 ID 更简单高效。如果是微服务、需要离线生成 ID 或希望对外提供不透明标识符,则选用 UUID v7。混合模式(内部整数 + 外部 UUID)能兼顾两者优势。
Q:UUID 主键比整数慢多少?
A:在高写入负载(每秒万次以上插入)和千万级数据量下,UUID v4 的插入速度可能慢 2-5 倍。而 UUID v7 的差距已缩小至 1.1-1.3 倍,对绝大多数应用而言可忽略不计。对于读多写少的场景,差异微乎其微。
Q:主流 ORM(如 Prisma, Django)支持 UUID 吗?
A:完全支持。Django 可用 models.UUIDField(primary_key=True),Prisma 可用 @default(uuid())。对于 UUID v7,建议在应用代码中生成,而非依赖 ORM 默认行为。
Q:在 URL 中暴露 UUID 安全吗?
A:UUID v4 是安全的,它不泄露任何信息。UUID v7 会暴露毫秒级的创建时间,需根据业务场景评估风险。两者均不会泄露记录总数或允许枚举。对安全性要求极高的场景,可考虑在暴露前对 UUID 进行加密或哈希处理。
Q:如何从自增 ID 迁移到 UUID 主键?
A:这是一个重大操作。基本步骤是:添加 UUID 列 -> 为现有数据填充 UUID -> 更新外键引用 -> 切换主键。对于大表,务必使用在线 DDL 工具(如 MySQL 的 pt-online-schema-change 或 PostgreSQL 的 pg_repack)以避免长时间锁表。
常见问题解答 (FAQ)
Q: GUID 和 UUID 是完全一样的东西吗?
A: 功能上,是的。两者都是 128 位标识符,拥有相同的字符串格式。唯一的技术差异在于二进制表示的字节序:微软用混合端序,RFC 标准用大端序。在字符串形式下,它们完全相同且可互换。
Q: 能在 PostgreSQL 里把 C# 生成的 GUID 当作 UUID 使用吗?
A: 完全可以。只要以字符串形式(如 "550e8400-e29b-41d4-a716-446655440000")传递,.NET 的 Guid 在 PostgreSQL 中就能完美作为 UUID 使用,数据库驱动会自动处理。只要避免在手动进行二进制转换时不考虑字节序差异即可。
Q: 为什么微软不直接用 UUID,而要另起炉灶叫 GUID?
A: 微软开发 GUID 的时间点与 UUID 规范的正式标准化几乎是同步的。COM 架构迫切需要一套自己的唯一标识方案,于是微软采用了自家的品牌命名。等到 RFC 4122 正式确立 “UUID” 这个术语时,“GUID” 早已深深植根于整个 Windows 生态系统,难以更改了。
Q: 字节序的差异真的会导致 Bug 吗?
A: 会,但仅限于特定场景:比如直接对 SQL Server 的 GUID 二进制数据和 PostgreSQL 的 UUID 二进制数据进行比较,或者在跨平台工具中直接读取 Windows 注册表的二进制 GUID。在字符串层面,没有任何区别,也绝不会出错。如今大多数应用都使用字符串表示,因此这个问题在实践中很少遇到。
Q: API 文档里该用哪个术语?
A: 用 UUID。它是 RFC 标准术语,被所有平台和语言所公认。即使你的团队主要使用 .NET,对外的 API 文档也应该采用这种通用术语。你的 .NET 开发者能理解 “UUID”,但并非所有人都知道 “GUID”。
跨平台陷阱
.NET 到 PostgreSQL
// C# 生成一个 GUID
var id = Guid.NewGuid();
// 当作为 UUID 参数传给 PostgreSQL 时,驱动会自动处理转换
// 但如果你手动转成字节数组,就要小心字节序!
byte[] bytes = id.ToByteArray(); // 这是混合端序!
// 要转换成 RFC 标准的大端序字节数组:
byte[] rfcBytes = new byte[16];
// 反转前4字节
rfcBytes[0] = bytes[3]; rfcBytes[1] = bytes[2];
rfcBytes[2] = bytes[1]; rfcBytes[3] = bytes[0];
// 反转第4-5字节
rfcBytes[4] = bytes[5]; rfcBytes[5] = bytes[4];
// 反转第6-7字节
rfcBytes[6] = bytes[7]; rfcBytes[7] = bytes[6];
// 复制剩余字节
Array.Copy(bytes, 8, rfcBytes, 8, 8);
SQL Server 到 Web API
SQL Server 的 UNIQUEIDENTIFIER 以微软的混合端序存储二进制数据。但当通过 JSON API 暴露时,其字符串表示与 UUID 完全相同。对于字符串,无需任何转换;只有在进行二进制比较时才需注意。
Java 到 .NET
Java 的 java.util.UUID 和 .NET 的 System.Guid 在处理字符串格式时都能无缝工作。只有在直接比较二进制表示时才会出现问题。
其他唯一标识符格式
在编写严谨的技术文档时,UUID 是更准确、更通用的术语,因为它指向了实际的国际标准(RFC 9562)。而 GUID 则是一个厂商特定的名称。
在评估唯一标识符方案时,可以看看 GUID/UUID 与其他选项的对比:
| 格式 | 大小 | 可排序 | 标准 | 适用场景 |
| UUID v4 | 128-bit | 否 | RFC 9562 | 通用场景 |
| UUID v7 | 128-bit | 是 | RFC 9562 | 数据库主键 |
| ULID | 128-bit | 是 | 社区规范 | 时间排序ID |
| Nanoid | 可配置 | 否 | 社区规范 | 短链接、URL安全ID |
| Snowflake ID | 64-bit | 是 | Twitter/Discord | 高吞吐分布式系统 |
| KSUID | 160-bit | 是 | Segment | K-可排序唯一ID |
| CUID2 | 可变 | 是 | 社区规范 | 安全、抗碰撞 |
每种方案都有其独特优势,而 UUID 凭借其最广泛的生态系统支持和标准化程度,依然是首选。
参考链接:
UUID-Cn
GUID-Cn
rfc-4122
rfc-9562