分库分表后auto_increment无法保证全局唯一,因其在各库表独立递增;推荐snowflake变种结合数据库号段预分配,结构为timestamp+machine_id+seq,并需处理时钟回拨与跨机房一致性。

为什么不能直接用 AUTO_INCREMENT
分库分表后,AUTO_INCREMENT 在每个库/表里独立递增,必然产生重复ID。比如两个库的主键都从 1 开始,插入时根本无法保证全局唯一——这不是配置问题,是架构层面的硬伤。
常见错误现象:Duplicate entry '123' for key 'PRIMARY',尤其在跨库写入或数据迁移后高频出现。
- 单库单表下
AUTO_INCREMENT安全,但一旦水平拆分,它就退化为“本地自增”,不再具备全局语义 - 即使给不同库设不同
auto_increment_offset和auto_increment_increment,也难应对动态扩缩容和故障恢复 - 时间戳类ID(如
UNIX_TIMESTAMP())在高并发下极易冲突,尤其毫秒级精度不够用
推荐方案:Snowflake 变种 + 数据库号段预分配
纯 Snowflake(如 Twitter 的 64 位结构)依赖机器 ID 和时钟,但在容器化、K8s 动态调度环境下,worker_id 难以稳定分配;纯数据库号段(如 Leaf-segment)又引入中心化瓶颈。折中做法是:用 MySQL 做号段托管,业务层生成 ID,避免每次发号都查库。
实操建议:
- 建一张专门发号表:
id_generator,字段含biz_tag(业务类型)、max_id(当前号段最大值)、step(每次取多少,建议 1000–10000) - 用
UPDATE ... WHERE max_id = ?原子更新获取新号段,失败则重试(不是SELECT + UPDATE,会丢并发) - 应用内存缓存已领取的号段,用尽再取;注意进程重启后需重新拉取,避免 ID 回退
- 生成的 ID 推荐结构:
timestamp(32bit) + machine_id(10bit) + seq(12bit),其中machine_id可从配置或服务注册中心读,不依赖物理机
替代方案:UUID / ULID 要慎用
UUID_SHORT() 依赖 MySQL server_id 和 server_startup_time,在主从切换或重启后可能重复;UUID() 字符串太长(36 字节),索引效率差,且无序导致 B+ 树频繁分裂。
如果坚持用 UUID 类型,至少做到:
- 用
UUID_TO_BIN()存为BINARY(16),节省空间并提升查询性能 - 避免用作主键聚簇索引,改用
INT/BIGINT自增列做主键,uuid字段单独加唯一索引 -
ULID比 UUID 更优(时间有序、更短),但 MySQL 原生不支持,需应用层生成并校验,无法靠 DB 强约束
最容易被忽略的点:时钟回拨与跨机房一致性
哪怕用了 Snowflake 或其变种,NTP 同步延迟、虚拟机休眠、云厂商宿主机时钟跳变,都会导致 timestamp 回拨——轻则 ID 重复,重则服务拒绝发号。
必须处理:
- 检测到时钟回拨时,阻塞等待(非无限等,设超时),或降级到备用策略(如 DB 号段)
- 多机房部署时,各机房的
machine_id必须全局唯一,不能只按物理机编号,建议结合机房 ID + 序号编码 - 号段表本身要做高可用:主从强同步 + 写节点自动发现,否则号段分配失败会导致整个业务写入中断











