自增主键的第一个阵亡
分库分表后最先坏掉的不是查询,是主键。单表时代 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 就从「一个编号」升级成「一张会走路的名片」。
主键问题解决,分片系统还剩两座大山:数据量继续涨了怎么扩容、老数据怎么迁到新架构。下一篇先讲扩容——预分片思想如何在设计期就为未来的自己铺路。
评论 (0)