一、为什么分布式 ID 是个难题
在单体数据库时代,自增主键(AUTO_INCREMENT)就够用了。但系统一旦拆成多节点、多库分表,自增主键立刻撞墙:两个节点会生成相同的 1、2、3,合并时冲突。
于是业界需要一种"不依赖中心、各节点各自生成、全局不重复"的 ID。主流方案分成两派:
- UUID 派:拿一段标准化的随机/时间戳串,128 位,跨语言跨库即插即用。
- 雪花派(Snowflake):把"时间戳 + 机器号 + 序列号"拼进一个 64 位整数,趋势递增、带时间语义。
下面先分别拆开看,再正面 PK。
二、两大流派总览
| 维度 | UUID(以 v4 / v7 为代表) | 雪花算法 Snowflake |
|---|---|---|
| 位宽 | 128 位 | 64 位(long 型) |
| 形态 | 带连字符的十六进制串(如 550e8400-e29b-41d4-a716-446655440000) |
一个长整型数字(如 1765383830000000000) |
| 核心思想 | 靠足够大的随机空间保证不重复 | 靠"时间 + 节点 + 序号"组合保证不重复且有序 |
| 是否有序 | v4 完全无序;v7 时间有序 | 趋势递增(按时间) |
| 是否依赖时钟 | 否 | 是(强依赖本机时钟) |
| 是否依赖网络/中心 | 否 | 否(但需分配机器 ID) |
| 标准 | RFC 4122 / RFC 9562 | Twitter 开源算法,无官方标准 |
三、UUID:128 位的标准标识符
UUID(Universally Unique Identifier)由 RFC 4122 定义,共 128 位,通常以 8-4-4-4-12 的十六进制呈现为 32 字符(加连字符 36 字符)。常见版本:
- v1 :时间戳 + MAC 地址。会泄露网卡 MAC,隐私风险大,基本不用。
- v4 :122 位密码学强随机数。最常见的版本,靠概率保证唯一(碰撞概率约
1/2^61,可忽略)。 - v6 / v7 / v8 :RFC 9562(2024-04 发布)新增。其中 v7 把 48 位毫秒时间戳放在最前面,其余为随机位------天然时间有序,专门用来当数据库主键。
- v8:自定义字段,留给业务扩展。
示例(v4,浏览器 crypto.randomUUID() 生成):
text
550e8400-e29b-41d4-a716-446655440000
优点 :无需协调、跨语言跨库原生支持、断网也能生成。 缺点:v4 完全无序,当主键时 B-tree 索引会频繁分裂、碎片化(这是 MySQL InnoDB / SQL Server 上的经典坑)。
四、雪花算法:64 位时间戳拼装
Snowflake 是 Twitter 2010 年开源的分布式 ID 算法。它把一个 64 位 long 拆成三段(最高位符号位恒为 0):
text
0 | 0000000000 0000000000 0000000000 0000000000 0000000000 0 | 0000000000 | 000000000000
|←────────────── 41 位时间戳(毫秒)──────────────→|← 5+5 机器ID →|← 12 位序列号 →|
(机房+机器,共 1024 节点)
- 41 位时间戳 (毫秒):从自定义纪元起算,可用约 69 年(到 2082 年左右)。
- 10 位机器 ID :通常 5 位数据中心 + 5 位机器,最多部署 1024 个节点。
- 12 位序列号 :单节点每毫秒最多生成 4096 个 ID ,理论单机 QPS ≈ 409 万。
核心生成逻辑(简化 Java):
java
long id = ((timestamp - twepoch) << 22) // 时间戳左移
| (workerId << 12) // 机器 ID
| sequence; // 序列号
优点 :本地生成无网络依赖、性能极高、趋势递增、自带时间语义(ID 里能读出生成时刻)。 缺点 :强依赖本机时钟------时钟回拨(NTP 校时、手动改时间)会导致 ID 重复 ;机器 ID 需靠注册中心(ZK/Nacos)分配,配错会冲突;64 位长整型在前端 JS(number 只有 53 位精度)里会丢失精度,通常要转成字符串传输。
业界改进:百度 UidGenerator(环形数组缓存序列号、弱化时间依赖)、美团 Leaf-snowflake(ZK 管机器 ID + 时钟回拨检测)、滴滴 Tinyid(号段模式)。
五、UUID vs 雪花:正面对比
| 对比维度 | UUID(v4/v7) | 雪花算法 Snowflake |
|---|---|---|
| 位宽 / 存储 | 128 位,二进制 16 字节 | 64 位,8 字节(更省) |
| 文本长度 | 36 字符(v4) | 19~20 位十进制串 |
| 有序性 | v4 无序;v7 有序 | 趋势递增 |
| 全局唯一保障 | 概率保证(随机空间极大) | 时间+节点+序号组合,确定性唯一 |
| 是否依赖时钟 | 否 | 是,时钟回拨会翻车 |
| 机器 ID 管理 | 不需要 | 需要(易冲突,需中心分配) |
| 前端友好度 | 字符串直传,OK | 64 位整数需转字符串(防 JS 精度丢失) |
| 生成性能 | 极高(本地随机) | 极高(本地拼接,≈409 万 QPS/节点) |
| 时间可推导 | v7 可、v4 不可 | 可(ID 里含时间戳) |
| 跨语言/库生态 | RFC 标准,几乎全原生支持 | 各语言有实现,但无统一标准 |
| 安全/可预测 | v4 不可预测;v7 泄露创建时间 | 连续可预测,会暴露订单量/用户量 |
六、怎么选:决策表
- 选 UUID(尤其 v7)当 :单库或多语言微服务、不想运维机器 ID、希望直接复用数据库原生
uuid类型、接受 16 字节存储。新项目首选 UUID v7(有序 + 标准)。 - 选雪花(或 Leaf/Tinyid)当 :超高并发、需要极致紧凑的 64 位整数主键、内部系统不暴露 ID、且有能力处理好时钟回拨与机器 ID 分配(有运维/基建支撑)。
- 都别用 v4 当大表主键:无序写入的索引碎片问题在千万行后会很明显。
一句话:要省心、跨语言、标准 → UUID v7;要极致紧凑、可控、有基建兜底 → 雪花。
七、折中方案:UUID v7 与 ULID
如果你既想要"时间有序"(解决索引碎片),又想要"UUID 那套 128 位、标准、跨库即用"的好处,有两个现成选择:
- UUID v7 :RFC 9562 标准,前 48 位放毫秒时间戳,后 74 位随机。格式仍是 UUID,能直接放进既有
uuid列。PostgreSQL 18 已原生uuidv7(),Python 3.13 有uuid.uuid7()。 - ULID :128 位,但用 26 字符的 Crockford Base32 表示(无连字符、大小写不敏感、去掉了易混的 I/L/O/U)。前 10 字符是 48 位毫秒时间戳、后 16 字符是 80 位随机数,字典序即时间序,比 UUID 短 10 个字符,URL/日志更友好。
简单说:UUID v7 = 标准里的有序方案;ULID = 更短更友好的有序方案。两者解决的是同一个问题,新项目优先 v7(生态最全),要短字符串就上 ULID。
八、动手试试:在线工具
不用装库,浏览器里就能生成对比:
- 👉 UUID V7生成器 (批量 1000 个,本地生成不上传):0x0bee.com/zh-CN/tools...
在你的浏览器本地完成,关掉网线照样能生成,令牌不会发到服务器。
九、常见问题 FAQ
Q1:雪花 ID 在 JavaScript 里为什么精度丢了? A:JS 的 number 只有 53 位有效数字,而雪花是 64 位整数。后端务必把 ID 序列化成字符串再传给前端,否则末尾几位会被四舍五入。
Q2:UUID v4 当主键真的不能用吗? A:小表无所谓;大表(千万行+)会因无序插入导致 B-tree 碎片、写入劣化。要么换 v7/ULID,要么用 BINARY(16) 存储并在应用层排序。
Q3:雪花时钟回拨怎么处理? A:回拨很小(如 <5ms)就等待追平;回拨较大则拒绝生成或切换备用时钟;Leaf-snowflake 还会在启动时用 ZK 校验上次时间,回拨直接启动失败。
Q4:UUID v7 和 ULID 该选哪个? A:同解决有序 ID 问题。技术栈已有原生 uuid 类型/类库 → 选 v7;要更短、URL 友好的文本 → 选 ULID。两者都是 128 位、二进制存储成本相同。
Q5:雪花 ID 会暴露业务数据吗? A:会。连续 ID 可被竞品推算出订单量、用户增速;且 ID 内嵌时间戳。对外暴露的场景建议用随机性更强的 UUID,或对 ID 做不可逆变换。
十、总结
UUID 和雪花算法不是谁替代谁,而是适用面不同:
- UUID(v7) :标准、跨语言、省心,适合大多数现代后端,新项目默认推荐。
- 雪花(Snowflake/Leaf):紧凑 64 位、可控、需基建兜底,适合超高并发且对内不暴露 ID 的场景。
- ULID:在"有序 + 短字符串"上比 UUID 更友好,是 v7 之外的轻量备选。
选型记住一条主线:要标准省心选 UUID v7,要极致紧凑且有运维能力选雪花,要短而有序选 ULID。