连载中 12/20

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

2026-07-24 · 861 阅读 · 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)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 4 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞