刚接触分布式系统时,我也曾被分库后的主键问题搞到崩溃------明明在单库中好好的自增主键,一到多库环境就频繁"撞车",两条不同的数据居然有相同的ID!后来才明白,这不是我的错,而是分库架构下自增主键的天然缺陷。今天咱们就聊聊怎么解决这个问题。
一、为啥分库后自增主键会"撞车"?
分库就像把一个大图书馆的书分到多个小图书馆里。每个小图书馆(分库节点)都有自己的编号规则:
- 图书馆A的书按1、2、3...编号
- 图书馆B的书也按1、2、3...编号 当你想把两个图书馆的书合在一起时,就会发现两本不同的书都叫"编号1",这就是主键冲突。
在数据库里,每个分库节点都有独立的自增计数器,都从1开始计数,自然会生成重复的ID。
二、5种全局唯一ID生成方案,总有一款适合你
方案1:UUID/GUID------最简单但有坑
UUID就像"随机生成的全球唯一身份证号",比如 550e8400-e29b-41d4-a716-446655440000 。
优点 :
-
超级简单,本地生成,不用依赖任何服务
-
性能高,没有网络开销 缺点 :
-
太长(32个字符),占存储空间
-
无序(随机字符串),数据库索引会频繁分裂,影响性能
-
可读性差,日志里看到一串乱码,根本不知道是哪条数据 适合场景 :临时表、非核心业务表,对性能和存储要求不高的场景
方案2:数据库自增段------小规模系统够用
这个方案给每个分库节点分配独立的"编号区间",就像给每个图书馆分配不同的编号段:
-
图书馆A:1、4、7、10...(步长3,起始1)
-
图书馆B:2、5、8、11...(步长3,起始2)
-
图书馆C:3、6、9、12...(步长3,起始3) 优点 :
-
ID是有序数字,对索引友好
-
存储成本低
-
实现简单(MySQL可通过 auto_increment_offset 和 auto_increment_increment 设置) 缺点 :
-
扩展性差,新增节点需要调整所有节点的编号规则
-
节点数量固定,无法动态扩容 适合场景 :节点数量固定、未来不打算扩容的小型分布式系统
方案3:雪花算法(Snowflake)------推荐的主流方案
雪花算法就像给每个ID打上"时间+地点+序号"的标签,生成64位数字:
-
1位符号位(固定0)
-
41位时间戳(毫秒级,能用约69年)
-
10位机器码(可标识1024个节点)
-
12位序列号(同一毫秒最多生成4096个ID) 优点 :
-
ID有序递增,对索引非常友好
-
性能极高,本地生成,单机每秒可生成百万级ID
-
扩展性好,10位机器码支持1024个节点 缺点 :
-
依赖服务器时钟,如果时钟回拨(比如NTP同步)可能生成重复ID
-
机器码需要手动配置,避免冲突 适合场景 :绝大多数分库分表场景,特别是对高并发、有序性要求高的系统
方案4:Redis自增------有Redis环境可以用
利用Redis的 INCR 命令(原子性自增)生成ID,就像有一个"全局计数器"。
优点 :
-
实现简单
-
ID有序,对索引友好
-
Redis性能高,单机每秒可处理万级请求 缺点 :
-
依赖Redis可用性,Redis宕机则无法生成ID
-
每次生成ID都要访问Redis,有网络开销 适合场景 :已有Redis集群,且对可用性要求不是特别极致的系统
方案5:分布式ID生成器------大厂都在用
像美团Leaf、百度UidGenerator这样的专业工具,都是基于雪花算法或号段模式的优化实现。
-
美团Leaf :支持号段模式(批量从数据库拿ID)和雪花模式(优化时钟回拨问题)
-
百度UidGenerator :用"环缓冲"预生成ID,解决高并发性能波动 优点 :
-
兼顾高并发、有序性和扩展性
-
解决了时钟回拨等边界问题
-
支持动态扩容 缺点 :
-
接入复杂度比前面的方案高 适合场景 :大型分布式系统,对ID生成有极高要求的场景
三、怎么选?一张表帮你做决定
需求场景 推荐方案 原因 简单至极,不关心性能和存储 UUID 实现最简单 节点固定,小规模系统 数据库自增段 配置简单,无需额外组件 高并发、有序性、可扩展性要求高 雪花算法/分布式ID生成器 性能好,ID有序,支持扩容 已有Redis集群 Redis自增 利用现有资源,实现简单
四、新手避坑指南
总结
分库分表后的主键问题,本质是需要一个"全局唯一的计数器"。选择方案时,要结合系统规模、并发量、扩展需求和现有技术栈来考虑。
对于大多数开发者来说,雪花算法或其优化版本(如美团Leaf、百度UidGenerator)是最稳妥的选择,它们兼顾了唯一性、性能和可扩展性,能满足绝大多数分布式场景的需求。
下次遇到分库主键"撞车",别慌,试试这些方案吧!