连载中 12/20

分片友好的主键:ID 里埋下路由基因

2026-07-24 · 865 阅读 · 0 评论 · 0 赞

自增主键的第一个阵亡

分库分表后最先坏掉的不是查询,是主键。单表时代 AUTO_INCREMENT 无脑好用;分片之后 32 张表各自从 1 数起,ID 撞车是必然事件——第 7 篇接入实战里的那条坑,根源就在这。分片环境对主键提出了三重要求:全局唯一(跨 32 张表不冲突)、趋势递增(B+ 树偏爱顺序写入)、最好自带路由信息(不查库就知道数据在哪个分片)。

为什么趋势递增这么重要

第二条要求值得单独讲透。MySQL 系列第 3 篇讲过 InnoDB 是聚簇索引组织表——数据按主键顺序物理存放。主键趋势递增时,新行永远追加到 B+ 树最右侧,写入顺序又快又省页;主键随机(典型如 UUID)时,新行被随机插入树的中部,页分裂频繁、缓存命中差、碎片膨胀,写入性能能差出数倍。所以「全局唯一」的候选里,UUID 这类随机 ID 在 InnoDB 主键位上基本出局,剩下的正解是号段式与雪花式——发号服务、时钟回拨、workerID 分配那一整串工程问题,深挖起来足够单独开一个系列,这篇先立住选型结论:中型系统号段发号器最省心,高并发实时发号用雪花算法加防护,细节留给那个未来的系列。

第三重身份:会走路的 ID

前两重是「唯一且不伤索引」,第三重才是分片主键的灵魂:ID 里埋下分片键的基因。第 5 篇的基因法在这里落地——生成订单号时,把买家 ID 的哈希低几位直接编进号码:

// 订单号 = 秒级时间 | 雪花段 | buyer基因(10bit)
long gene = (buyerId.hashCode() & 0x3FF);          // 分片基因
long orderNo = (tsSecond << 30) | (seq << 10) | gene;

// 按订单号查询:直接提取基因算路由,不用先查映射
long slot = extractGene(orderNo) % 1024;           // 与按 buyerId 路由一致

这一埋,解决了三个痛点:按订单号的查询不用再广播——客服、支付回调、对账系统拿号就能定位分片;订单号与买家分片永远一致——同一订单的数据天然同片,绑定表 JOIN 稳定成立;扩容时基因不变——路由算法再怎么演进,埋在 ID 里的基因是永久路标。代价是号码里编码了路由信息, ID 格式从此是「契约」——一旦上线,位数与段的含义再改就要兼容全量历史数据,设计期要把每一段的宽度想清楚、留够余量。

落地的检查清单

分片主键的落地清单五条:禁用 AUTO_INCREMENT——分片表全部改为应用层发号;选型发号器——号段或雪花,别用随机 UUID 当 InnoDB 主键;埋基因——把核心查询维度的哈希编进 ID;格式即契约——段的位宽、含义写进团队文档,改格式要走评审;旧数据兼容——迁移期老 ID 不带基因,路由层要有「无基因走映射」的兜底分支。五条做到,ID 就从「一个编号」升级成「一张会走路的名片」。

主键问题解决,分片系统还剩两座大山:数据量继续涨了怎么扩容、老数据怎么迁到新架构。下一篇先讲扩容——预分片思想如何在设计期就为未来的自己铺路。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#分布式ID#雪花算法#基因法#趋势递增#主键设计

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞