从单机到分布式:自增主键迁移的全局唯一方案盘点

阿乐
阿乐 管理员 黑卡会员
发布于 2026-09-11 18:46 ·3 浏览 ·0 回复

单库单表时代,`AUTO_INCREMENT` 是省心设计:唯一、递增、占空间小、B+树友好。但业务一拆库分表,或者多活多写,自增主键立刻变成灾难:两个库都从 1 开始,合并就撞主键;迁移时新旧系统并行,ID 冲突、外键断裂、索引重建。更麻烦的是,存量数据已经用了 int 自增,新方案不能只考虑新数据,还要把旧数据平滑带过去。所以选型时别只问“哪个发号器快”,先明确约束:全局唯一、趋势递增、高可用、可回滚、长度可控、业务无感。

方案一:多主步长自增

设置 `auto_increment_increment` 和 `offset`,让不同实例生成不重叠的 ID。优点是改配置就能用,ID 仍是数字且递增。缺点是扩容要改步长、容易留空洞,实例一多步长冲突风险高,只适合中小规模、同构数据库的过渡期。

方案二:号段模式

数据库里只存一个 `max_id`,每次取一批号段到内存,比如 `step=1000`。应用本地发号,DB 压力从“每行一次”降到“每千行一次”。配合双 buffer 预加载,可用性也不错。缺点是 ID 有空洞,服务重启可能浪费一段;DB 仍是弱依赖,要做高可用。对大多数分库分表业务,这是性价比最高的方案,美团 Leaf-segment、滴滴 Tinyid 都属于这类。

方案三:Redis/内存发号

用 `INCR/INCRBY` 原子递增,性能高、实现简单,还能按业务设前缀。但 Redis 必须考虑持久化与主从切换:AOF everysec 仍可能丢一段,主从切换可能回退。适合能容忍 ID 空洞、已有 Redis 集群且运维成熟的团队。

方案四:Snowflake 类算法

时间戳 + 机器位 + 序列号,本地生成,无网络依赖,趋势递增,是分布式 ID 的经典答案。真正难的不是算法,而是 workerId 分配和时钟回拨:机器位重复会直接撞 ID,回拨则可能重复发号。Leaf-snowflake、UidGenerator、Sonyflake 都在解决这些工程问题。它适合超大规模、多机房,但需要配套的注册、校验、告警体系。

方案五:UUID/ULID/UUIDv7

UUIDv4 无序,做主键会让 B+树插入随机化,页分裂严重;ULID 和 UUIDv7 带时间有序性,全局唯一且生成简单,适合日志、事件、消息等场景。但 128 位存储偏大,可读性差,若业务需要数字 ID 或对外暴露,通常还要加映射。

迁移时最容易踩的坑

第一,ID 类型别只升到 `bigint` 就完事,API 返回给前端要字符串化,否则 JS Number 精度丢失。第二,旧数据回填要保留 `old_id` 到 `new_id` 的映射,外键、索引、分片键一起改。第三,双写期间新老 ID 并存,查询要兼容两种形态;切换前做全量校验和灰度。第四,别把发号器做成单点,号段服务、Redis、Snowflake 都要有降级和监控。

怎么选

中小规模、单库分表:优先号段模式;已有成熟 Redis:可考虑 Redis 发号;超大规模、多机房:Snowflake + 统一 workerId 管理;对数字 ID 无强需求:ULID/UUIDv7。更稳的做法是把发号能力抽象成独立服务,业务只依赖接口,底层从号段平滑演进到雪花。

从单机自增到分布式全局唯一,本质是用可控的复杂度换取扩展性。没有银弹,只有约束下的取舍:先保证唯一和可用,再追求有序和性能,最后把迁移路径设计成可回滚、可灰度、可验证的工程流程。

本文转载自 阿乐技术社区,原文地址:https://www.leleweb.cn/thread-231.html
转载请注明出处,版权归原作者所有。

全部回复 0

还没有回复,来抢沙发~