一、UUID 是什么
UUID(Universally Unique Identifier,通用唯一识别码)是一个 128 位的数字,通常表示成 32 个十六进制字符,用连字符分成 5 段:
550e8400-e29b-41d4-a716-446655440000
格式是 8-4-4-4-12,共 36 个字符(含 4 个连字符)。这个格式几乎在所有系统里都是统一的——无论是 Windows 的 GUID、Java 的 UUID、还是 PostgreSQL 的 uuid 类型,长得都一样。
UUID 的目标是:在分布式系统中,不需要中心协调,也能保证生成的 ID 不重复。这一点非常关键,后面会详细讲。
二、UUID 的 5 个版本
UUID 规范(RFC 4122)定义了 5 个版本,每个版本的生成方式不同:
| 版本 | 生成方式 | 特点 | 典型场景 |
| v1 | 时间戳 + MAC 地址 | 时间有序,会暴露机器 MAC | 几乎不用 |
| v2 | DCE Security 版本 | 很少实现 | 基本不用 |
| v3 | 命名 + MD5 哈希 | 同输入同输出,可重现 | 历史遗留 |
| v4 | 随机数 | 最常用,完全随机 | 默认选择 |
| v5 | 命名 + SHA1 哈希 | 同输入同输出,比 v3 更安全 | 命名场景 |
v1:基于时间
用当前时间戳(纳秒级)+ 机器 MAC 地址生成。优点是时间有序,按 UUID 排序约等于按时间排序。缺点是会暴露机器的 MAC 地址,有隐私问题,而且同一台机器同一瞬间并发生成可能冲突。
v3 和 v5:基于命名
给定一个"命名空间 UUID"和一个"名字字符串",哈希一下得到 UUID。同样的输入永远产生同样的输出,适合"需要可重现"的场景。v3 用 MD5,v5 用 SHA1,现在推荐用 v5。
v4:基于随机数
最常用的版本。128 位里有 122 位是随机数(剩下 6 位是版本和变体标识),完全靠随机性保证唯一性。绝大多数语言的默认 uuid() 函数生成的就是 v4。
生成 v4 的 JavaScript 示例:
function uuidv4() {
return crypto.randomUUID();
}
console.log(uuidv4());
// "1b9e6f7a-3c4d-4e5f-8a2b-9c1d2e3f4a5b"
浏览器和 Node.js 都内置了 crypto.randomUUID(),直接调用即可。
三、为什么 v4 最常用
v4 之所以成为事实标准,原因有三:
- 简单:不需要时间戳、不需要 MAC 地址、不需要命名空间,只要随机数
- 安全:不暴露机器信息,不依赖网络
- 无状态:每次调用互相独立,没有时序依赖
但 v4 也有缺点:完全无序。这一点对数据库性能有影响,后面会展开讲。
四、碰撞概率有多低
v4 UUID 有 122 位随机性,可能的取值数量是 2^122,约等于 5.3 × 10^36。这个数字大到难以想象。
根据生日悖论计算:
| 生成数量 | 碰撞概率 |
| 103 万亿个 | 50% |
| 1 亿个 | 约百万分之一 |
| 100 万个 | 约十亿分之一 |
| 100 个 | 几乎为 0 |
换种说法:如果你每秒生成 10 亿个 UUID,连续生成 85 年,碰撞概率仍然小到可以忽略。
所以在工程实践中,UUID 碰撞几乎不需要担心。即使发生了,也极可能是随机数生成器本身有 bug,而不是 UUID 算法的问题。
五、UUID vs 自增 ID
这是数据库设计里最经典的争论之一。我们来对比一下。
自增 ID
1, 2, 3, 4, 5, ...
优点:
- 体积小(通常 8 字节 BIGINT)
- 数据库索引性能好(有序插入)
- 对人友好
缺点:
- 分库分表时无法保证全局唯一
- 容易被猜测(用户能看到
?id=1、?id=2,推断业务规模) - 依赖单点(数据库自增锁)
UUID
550e8400-e29b-41d4-a716-446655440000
优点:
- 全局唯一,不需要中心协调
- 可以在客户端生成,减少一次 DB 往返
- 不可猜测,不暴露业务规模
缺点:
- 体积大(16 字节,存储和索引都比 BIGINT 大)
- v4 无序,导致 B+ 树页分裂频繁,写入性能下降
- 对人不友好
对比表
| 维度 | 自增 ID | UUID(v4) |
| 长度 | 8 字节 | 16 字节 |
| 全局唯一 | 否 | 是 |
| 客户端生成 | 否 | 是 |
| 索引性能 | 好(有序) | 较差(随机) |
| 可猜测性 | 高 | 几乎不可猜测 |
| 分库分表 | 复杂 | 天然支持 |
| 对人友好 | 是 | 否 |
六、什么时候该用 UUID
根据上面的对比,可以总结出几个清晰的判断标准。
推荐用 UUID 的场景
- 分布式系统:多个服务、多个数据库同时写入,不能用自增
- 客户端先生成 ID:比如离线 App 创建记录,联网后再同步
- 需要不可猜测:比如邀请链接
?token=xxxx-xxxx - 数据需要合并:多个库的数据汇到一起,自增 ID 会冲突
推荐用自增 ID 的场景
- 单库单表:业务简单,没有分布式需求
- 对性能敏感:高并发写入场景
- 对外不暴露 ID:用业务字段(用户名、slug)对外展示
折中方案:UUID v7
为了解决 v4 无序导致数据库性能下降的问题,近年出现了 UUID v7(还在标准化中)。它把前 48 位用作时间戳,后面是随机数:
017f22e2-79b0-7cc3-98c4-dcfe0e23ce1d
这样既保留了 UUID 的全局唯一性,又有时间顺序,数据库写入性能和自增 ID 接近。未来几年 v7 很可能成为默认选择。
七、UUID 的常见使用场景
| 场景 | 说明 |
| 数据库主键 | 尤其是分布式数据库(Cassandra、MongoDB) |
| API 请求追踪 | 每个请求分配一个 UUID,日志全链路串联 |
| 文件名 | 用户上传文件时用 UUID 命名,避免重名 |
| 设备标识 | App 首次启动生成 UUID 作为设备 ID |
| 会话 token | 登录后生成 UUID 作为 session token |
| 事件 ID | 消息队列、事件溯源里每个事件的唯一 ID |
| 测试数据 | 自动化测试里生成假数据的主键 |
八、UUID 的存储建议
在数据库里存 UUID 时,有几种常见做法:
| 存储方式 | 体积 | 查询性能 | 说明 |
| 字符串(36 字符) | 36 字节 | 较差 | 可读但体积大,不推荐 |
| 字符串(无连字符) | 32 字节 | 较差 | 略好,但仍大于二进制 |
| BINARY(16) | 16 字节 | 好 | MySQL 推荐 |
| uuid 类型 | 16 字节 | 好 | PostgreSQL 原生支持 |
建议:除非有特殊需求,数据库里 UUID 应该用 16 字节的二进制存储,而不是 36 字符的字符串。两者体积差一倍多,索引和查询性能差距也很大。
九、实践推荐
日常开发中经常需要生成 UUID——做测试数据、生成临时 token、给文件命名等等。手写代码虽然不复杂,但用在线工具更直观,还能一次生成多个:
- UUID 在线生成工具:https://52tool.net/tools/code/uuid
支持 v1、v4、v5 多版本,批量生成,一键复制,还可以选择带不带连字符的格式,是开发调试和测试数据准备时的常用工具。