每个数据库行、每个API资源、每个分布式事件都需要一个ID。问题不在于生成ID,而在于选择一种格式,以避免六个月后Postgres索引碎片化、URL看起来像噪音的情况。
此处是完整的 UUID生成器对比:UUID v4、UUID v7、ULID、CUID2和Snowflake——它们是什么、在哪些情况下会失效,以及你应该使用哪一个。
UUID v4:安全的默认选项(但有一个大问题)
UUID v4包含128位随机性,格式为 550e8400-e29b-41d4-a716-446655440000。它被全球广泛理解,被所有编程语言、所有数据库和所有ORM框架所支持。
碰撞概率几乎为零——你需要每秒生成十亿个UUID,持续85年,才可能遇到50%级别的单次碰撞概率。在实际应用中这并非问题。
问题是排序。UUID v4是 完全随机的,这意味着在UUID索引的表中插入行会将写入操作分散到B树的各个位置。在大规模场景下,这会导致页面分裂、索引碎片化和写入性能下降。如果你每秒插入数千行到MySQL或Postgres表的UUID主键中,你就会感受到这一点。
UUID v4作为字符串长度为36个字符——在不编码的情况下不适用于URL,且相比其他方案显得臃肿。
UUID v7:UUID v4的更好兄弟
UUID v7解决了排序问题。它是一种时间有序的UUID,其中最高有效位编码毫秒时间戳,其余部分是随机的。结果是: 01875f3a-7b2d-7f8e-a3d1-4b2e6c1a0f93.
按时间顺序插入的行在索引中大致保持连续。这对于写入密集型工作负载来说是重大优势。UUID v7与现有的所有UUID基础设施兼容——相同的格式、相同的字段长度、相同的库支持要求——同时增加了可排序性。
RFC在2022年最终确定,库支持正在快速跟进。如果你已经使用UUID且无法更改架构,从v4切换到v7风险低且收益高。
ULID:最符合开发者需求的选项
ULID(通用唯一字典排序标识符)将48位时间戳和80位随机性编码为26个Base32字符: 01ARZ3NDEKTSV4RRFFQ69G5FAV.
其突出特点:
- 默认可排序 ——字典排序即时间顺序排序
- URL安全 ——没有连字符,没有特殊字符
- 不区分大小写 ——通过排除这些字符来避免
0/O且1/I歧义 - 紧凑 ——26个字符,相比UUID字符串的36个字符更短
对于没有遗留约束的新项目,ULID是最实用的选择。它在大多数场景下足够可排序,URL中足够短,且人类可以轻松复制粘贴而不会出错。
一个注意事项:如果在同一个毫秒内生成多个ULID,其单调排序顺序在单个进程内是保证的,但在分布式节点之间则不保证。对于大多数应用程序来说,这已经足够了。
CUID2:专为分布式系统设计
CUID2是CUID的后继者,从零开始重新设计,以增强分布式环境下的安全性和抗碰撞能力。一个CUID2看起来像: clh3uj5ln0000qzrmn831mbhe.
它使用一个组合时间戳、计数器、指纹(进程ID + 主机名)和随机字节的SHA-3哈希。指纹是关键区别——它专门设计用于防止在多个服务器上同时运行多个ID生成器时发生碰撞。
CUID2是 不可排序的。它优先考虑抗碰撞和不可预测性,而非时间顺序。如果你正在构建一个由不可信客户端或大量独立节点生成ID的系统,并且安全是关键,那么CUID2值得这种权衡。
对于大多数后端API来说,这都过于复杂。
Snowflake ID:高吞吐量,但你需要自行管理
Snowflake ID最初由Twitter发明,用于在分布式集群中每秒生成数百万个唯一ID。一个Snowflake ID是64位整数:时间戳(41位)+ 数据中心ID(5位)+ 机器ID(5位)+ 序列号(12位)。
它们是可排序的,紧凑(适合放入 BIGINT),并且速度极快。Discord、Instagram和许多高规模系统都使用这种模式。
问题在于:你需要管理机器ID。这意味着需要一个协调服务(如ZooKeeper、etcd或数据库表)来为每个ID生成器分配唯一的机器ID。如果两个节点共享同一个机器ID,就会发生碰撞。正确设置这一点并不简单,且持续维护会带来操作开销。
除非你每秒生成数十万个ID,否则这种复杂性并不值得。
对比分析
| UUID v4 UUID v4(随机 UUID)是一种通过随机数生成的全球唯一标识符,通常用于数据库记录、文件系统和网络应用程序中。它由32位二进制数组合而成,并以“-”分隔为五个部分:8-4-4-12。 **注意:** 此版本的UUID通常使用RFC 4122标准生成。 | UUID v7 | 有效识别号 | CUID2 | Snowflake | |
|---|---|---|---|---|---|
| 可排序 | 不 | 是的 | 是的 | 不 | 是的 |
| URL安全 | 否(无连字符) | 否(无连字符) | 是的 | 是的 | 是(整数) |
| 抗碰撞 | 非常高 | 非常高 | 高的 | 非常高 | 高(需协调) |
| 复杂 | 没有任何 | 没有任何 | 没有任何 | 低的 | 高的 |
| 典型应用场景 | 遗留系统、通用用途 | 数据库主键 | API、URL、新项目 | 分布式/不可信客户端 | 高吞吐量系统 |
在Node.js中生成每个ID
// UUID v4
import { v4 as uuidv4 } from 'uuid';
console.log(uuidv4()); // '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
// UUID v7
import { v7 as uuidv7 } from 'uuid';
console.log(uuidv7()); // '01875f3a-7b2d-7000-8000-4b2e6c1a0f93'
// ULID
import { ulid } from 'ulid';
console.log(ulid()); // '01ARZ3NDEKTSV4RRFFQ69G5FAV'
// CUID2
import { createId } from '@paralleldrive/cuid2';
console.log(createId()); // 'clh3uj5ln0000qzrmn831mbhe'
// Snowflake (using @socialgouv/nextid for simplicity)
import Snowflake from '@socialgouv/nextid';
const snowflake = new Snowflake(1n); // machine ID = 1
console.log(snowflake.nextId().toString()); // '1641024000000000001'
你可以直接使用 IO Tools UUID生成器 ——它支持UUID v1到v7,并支持批量生成,无需安装任何软件。
你应该选择哪一个?
以下是实际建议,而非“视情况而定”的敷衍说法:
- 新项目,无遗留约束: 使用 有效识别号。可排序、URL安全、紧凑。它是最佳默认选择。
- 已使用UUID,需要更好的数据库性能: 迁移到 UUID v7。直接升级,大幅改善索引性能。
- 由客户端或在不可信的分布式节点上生成ID: 使用 CUID2。指纹机制使其在对抗性条件下仍能保持抗碰撞的鲁棒性。
- 高吞吐量平台(每秒10万以上ID),并愿意管理机器ID: 使用 Snowflake。否则,无需考虑。
- UUID v4: 仅在维护遗留代码且无法更改格式时使用。不要再为新表使用它。
默认使用UUID v4的时代已经结束。对于大多数场景,ULID是新的默认选择,如果你已经使用UUID,那么升级到UUID v7是正确的路径。选择一个并坚持下去。
