UUID、自增 ID 还是雪花 ID:分布式主键怎么选

最后更新:2026-09-14

主键选型是一个「改起来很贵」的决定:它同时影响数据库写入性能、对外接口的安全性、以及未来多库合并的难度。四种主流方案各有明确适用面,选错的代价通常在两三年后才集中显现。

判断的落脚点不是「哪个更先进」,而是三个问题:写入是否有序、对外是否可枚举、是否需要跨节点生成。

一、四种方案的定位

  • 自增整数:最短、写入完全有序、索引效率最高。缺点是对外可枚举(能推算业务体量),且多库写入时会发生冲突,需要额外规划步长;
  • UUID v4:无中心协调、生成简单,天然适合多节点。缺点是无序,作为聚簇索引主键会造成随机写入,数据量大后性能退化明显;
  • 时间有序 UUID(v6 / v7):兼顾无序生成与近似有序写入,是近年较受推荐的折中方案,代价是长度仍为 16 字节;
  • 雪花 ID 类方案:时间戳 + 机器位 + 序列号组成 64 位整数,有序、省空间、无需中心协调。代价是依赖时钟,且需要分配机器位。

二、关键维度对比

  • 有序性:自增与雪花最好,时间有序 UUID 次之,UUID v4 最差;
  • 长度:自增 4–8 字节、雪花 8 字节、UUID 16 字节。索引体积的差异会在亿级数据上放大成显著的存储与缓存成本;
  • 对外安全:自增完全可枚举,雪花可通过时间位推断生成时刻与业务量,UUID 不可枚举;
  • 跨库合并:UUID 与雪花天然无冲突,自增需要预先规划;
  • 依赖:自增依赖数据库,雪花依赖时钟与机器位分配,UUID 只依赖随机源。

三、雪花 ID 的时钟回拨问题

雪花类算法的前提是「时间单调向前」。当服务器时间被人工校准、或发生闰秒处理与虚拟化迁移时,时钟可能回拨,此时同一毫秒内的时间戳会重复,生成的 ID 可能与之前重复。

工程上的处理方式有三种:检测到回拨时短暂等待直到时间追平(简单,但有延迟);回拨超过阈值就拒绝服务并告警(安全,可用性受影响);额外引入机器位或序列位区分(复杂)。无论选哪种,都必须有显式的处理逻辑与监控——静默接受回拨是产生重复主键的直接原因。

同时要注意:雪花 ID 是 64 位整数,超出 JavaScript 的安全整数范围,传到前端时必须序列化为字符串,否则会静默丢精度。

四、按场景的推荐

  • 单库单表、内部使用、不对外暴露 → 自增整数,最省事也最高效;
  • 需要对外暴露标识、且不希望被枚举 → 自增做内部主键 + UUID 或随机串做对外标识,兼顾写入性能与安全性;
  • 多节点写入、数据量大、对写入性能敏感 → 雪花 ID 或时间有序 UUID;
  • 日志、事件、需要离线生成再合并 → UUID;
  • 已经上线且主键为随机 UUID → 不必急于迁移,先评估实际写入压力;确需优化时优先考虑「新增有序列并在新表使用」,而不是直接改主键。

五、常被忽略的两点

  • 主键与唯一业务键应当分开:主键服务于存储与关联,业务键(订单号、流水号)服务于对外识别与对账,两者的约束不同,混用会让后续变更非常被动;
  • 如果既要用有序主键又要防止枚举,可以对有序 ID 做可逆编码(如固定映射或进制变换),而不是更换整套 ID 方案——但要注意编码是可逆的,只能提高枚举门槛,不能提供保密性。

六、工具与延伸

需要生成标识符或高强度随机字符串时可用 UUID 与密码生成工具。各 UUID 版本的性质差异见《UUID 各版本详解》;大整数精度问题与《JSON 校验失败的 10 个常见原因》中的数字格式一节也直接相关。