连载中 5/20

分片键:一次选择,终身锁死

2026-07-20 · 1201 阅读 · 0 评论 · 0 赞

改不动的决策

分库分表的所有决策里,扩容可以重新设计、路由可以换中间件、表数可以从 64 加到 128,唯独分片键一旦选定,几乎是终身制。原因在于一条铁律:所有查询都必须能推导出数据在哪个分片。而查询是活着长出来的——今天按用户查订单,明天运营要按商家查,后天客服要按订单号查。分片键选了 user_id,所有不带 user_id 的查询,都会退化成扫全部分片的「全路由」,分片越多,扫得越惨。

所以选分片键的第一原则不是技术,是业务:把系统里频次最高、最重要的查询维度找出来,用数据说话——把慢查询日志和访问日志拉出来按 WHERE 条件聚类,哪个维度的查询占了八成,它就是候选。

经典难题:订单的三个主人

订单表是这个难题的教科书案例。一张订单至少有三个查询维度:买家(我的订单)、卖家(我的生意)、订单号(客服与支付回调)。三个维度互相独立,而分片键只能选一个——选 buyer_id,卖家的「店铺订单列表」就要扫全分片;选 seller_id,买家的「我的订单」同样遭殃。三种经典解法,按代价从小到大排:

解法一:冗余双写——订单数据写两份,一份按 buyer_id 分片、一份按 seller_id 分片,两个维度各自精准路由,代价是存储翻倍、写入要保证两份一致、任何字段变更都要双份同步。适合两个维度都高频的场景。解法二:异构索引——主数据按 buyer_id 分片,另建一套按 seller_id 组织的订单索引表(或直接喂给搜索引擎),索引只存定位信息,回主库取详情。ES 这类检索引擎天然适合干这个,代价是多维护一条数据同步链路。解法三:基因法——在生成订单号时,把 buyer_id 的哈希「基因」埋进去,订单号本身就能算出分片,按订单号查不再需要额外路由;再配合解法一或二覆盖卖家维度。基因法的订单号形如:

订单号 = 时间戳段 | buyer基因(hash(buyer_id) 低 10 位) | 序列段
路由:  slot = 订单号基因段 % 1024   // 与按 buyer_id 路由结果一致

选键的其余纪律

除了维度覆盖,分片键还有几条纪律。要分散:键的取值分布要均匀,用省份、状态这类低基数字段做分片键,等于给自己造热点;自增 ID 单调递增会让 Range 刀法出现写入热点,配 Hash 刀法则没问题。要常在:分片键必须在绝大多数查询和写入请求里天然可用——用户下单必带 user_id,天然贴合;如果分片键只在少数场景出现,数据写入就没了归属。要稳定:分片键的值不能变——数据一旦落片,改分片键等于删除加重新插入,业务上往往是灾难(换手机号都别想让用户数据搬家)。

把纪律合起来就是一句口诀:高频维度做键、基因法补洞、冗余或异构兜底。分片键定了,下一个问题是路由逻辑放在哪儿执行——写在代码里、坐在代理上,还是长在 SDK 里,下一篇比较三种落法。

503

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

#分片键#基因法#冗余双写#异构索引#路由

评论 (0)

相关推荐

连载中 12/20

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

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

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 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 赞