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 个常见原因》中的数字格式一节也直接相关。