当前位置:首页>排行榜>分库分表怎么选分片键?选错之后查询、事务和扩容都会变贵

分库分表怎么选分片键?选错之后查询、事务和扩容都会变贵

  • 更新时间 2026-09-30 08:53:29
分库分表怎么选分片键?选错之后查询、事务和扩容都会变贵

摘要:分片键不是把数据平均散开就够了。它决定请求能否单路由、事务是否跨库、热点是否集中以及未来怎样扩容。本文以订单系统为例,对比用户ID、订单ID和商家ID,给出Java路由代码、基因法设计与选键检查框架。

订单表突破十亿行,团队决定分成64个库表。有人提议按 order_id % 64,因为订单ID唯一且分布均匀。

上线后,用户查看“我的全部订单”要查询64个分片;商家对账也要扫全库;退款要跨分片查订单与支付。数据确实平均了,请求却被打散了。

分片键不是存储问题,而是整个访问路径的架构决定。

一张访问矩阵比参数讨论更重要

先列出核心请求:

请求                     频率      是否必须低延迟按订单ID查详情             高        是按用户ID查最近订单          很高      是按商家ID查待发货订单         高        是按时间统计全站交易           中        否按支付流水号反查订单         中        是

一个理想分片键应该让最高频、最关键的请求只访问一个分片。全站统计天然跨分片,可以交给数仓或汇总系统,不应反过来绑架在线库设计。

用户ID、订单ID、商家ID怎么选

按用户ID:用户订单列表和用户侧事务容易单分片;订单详情必须从订单ID找回用户ID,或让订单ID携带路由信息。大客户可能形成热点。

按订单ID:数据通常均匀,详情天然单路由;用户与商家列表成为跨分片查询。

按商家ID:商家履约和对账友好;超级商家造成容量与写热点,用户侧查询分散。

没有完美分片键。选择取决于哪个聚合是系统的主边界。订单C端系统可能优先用户,商家履约平台可能优先商家。

最小路由代码

分片数量为2的幂时,可以对混合后的哈希取掩码:

publicfinalclassShardRouter{privatefinalint shardCount;publicShardRouter(int shardCount){if (Integer.bitCount(shardCount) != 1) {thrownew IllegalArgumentException("shardCount must be power of two");        }this.shardCount = shardCount;    }publicintroute(long key){long x = key;        x ^= (x >>> 33);        x *= 0xff51afd7ed558ccdl;        x ^= (x >>> 33);return (int) x & (shardCount - 1);    }}

不要直接假设业务ID低位分布均匀。哈希混合能改善路由分布,但它也让范围扩容更复杂。

订单ID怎样携带路由信息

若主分片键是 user_id,但详情请求只有 order_id,可以:

  1. 维护 order_id → shard_id路由表;
  2. 把分片编号编码进订单ID的若干位;
  3. 先通过全局索引查路由;
  4. 要求调用方同时携带用户ID。

把分片位写进ID能减少一次查询,但暴露了物理结构。未来从64片扩到128片时,旧ID仍指向旧规则,需要版本位或兼容路由。

所谓“基因法”本质是让订单ID保留用户ID的一部分路由特征,使相同用户的订单稳定进入同一分片。它减少路由查询,代价是ID生成与迁移协议更复杂。

跨分片查询为何昂贵

查64个分片不是只多63次SQL。系统还要:

  • 并发获取连接;
  • 等最慢分片返回;
  • 合并排序与分页;
  • 处理部分分片超时;
  • 保证翻页期间数据变化后的稳定性。

LIMIT 20 OFFSET 10000在每个分片执行后再全局归并,成本尤其高。可以使用游标分页,为每个分片保存上次位置,或把查询模型异步投影到专用索引。

跨库事务怎样收敛

分片键应该尽量让需要原子提交的数据同库。订单、订单明细和订单事件若都按同一个订单路由,可以使用本地事务。

如果余额按用户分片、订单按商家分片,一次支付天然跨库。可以使用Saga、Outbox和幂等补偿,但复杂度、延迟和故障状态都会增加。

所以选分片键时要把事务边界画在图上,不能只跑一段均匀性测试。

扩容不是把模数从64改成128

直接改模后,大量Key路由变化。迁移期间新旧代码可能读写不同位置。

常见方案:

  • 逻辑槽位多于物理节点,扩容时迁移部分槽位;
  • 路由表带版本,由配置中心控制;
  • 双读、校验、切读、停旧写、清理的分阶段迁移;
  • 一致性哈希适用于部分Key-Value场景,但关系数据仍需处理事务和查询。

选键前的七个问题

  1. 最高频查询能否单分片;
  2. 核心事务能否落在同一分片;
  3. Key分布是否均匀,有没有超级租户;
  4. 是否存在持续递增造成写热点;
  5. 无分片键查询如何建立二级索引;
  6. 扩容时路由如何版本化;
  7. 数据错路由后如何检测和修复。

分片键一旦进入主键、SQL、缓存Key、消息分区和监控标签,修改成本会迅速扩大。真正好的分片键,不只让今天的数据均匀,还让最重要的请求、事务与未来迁移保持可控。

随机文章