SQL 水平分库分表(Sharding)的分片键选择与热点问题规避策略

1次阅读

分片键应选高频查询、高基数、业务强相关的字段(如user_id),避免递增时间戳或低基数字段;推荐预分片+逻辑分片映射,禁用纯时间range分表;跨分片查询需用es宽表等异步方案兜底。

SQL 水平分库分表(Sharding)的分片键选择与热点问题规避策略

分片键选错,数据刚上线就变热点

分片键不是随便挑个 idcreated_at 就能用的。选错最直接的表现是:新订单全写进 order_0 表,监控里它 CPU 95%、IO 满载,其他表却闲着——这就是典型的“热点”,本质是数据分布不均 + 查询/写入集中。

  • 别用递增时间字段(如 created_at)做哈希分片:MD5(created_at)UNIX_TIMESTAMP(created_at) % N 都不行,因为相邻时间戳哈希值可能扎,且新数据永远落在少数分片
  • 避免低基数字段(如 status 只有 0/1/2,region 只有 4 个值):取模后最多生成 4 个分片,根本起不到分散作用
  • 优先选高频查询 + 高基数 + 业务强相关字段:比如电商查订单必带 user_id,用户量千万级、分布天然较均匀,就是比 order_id(可能含业务前缀)更稳的选择
  • 验证方法很简单:抽样 10 万条线上数据,统计 user_id % 16 的余数分布,看是否在 0–15 之间偏差

哈希取模扩容必须停机迁移?那是没做预分片

user_id % 4 分 4 库,半年后要扩到 8 库,老数据得全量搬——这确实痛苦。但问题不在哈希本身,而在没预留扩展空间。

  • 真实可行方案是「逻辑分片 > 物理节点」:比如固定定义 1024 个逻辑分片(shard_id = crc32(user_id) % 1024),初始只用 4 台物理 DB,每台负责连续 256 个逻辑分片(0–255 → db0,256–511 → db1…)
  • 扩容时只需调整映射关系:加第 5 台 DB,把 db0 的 0–127 拆出去给它,不动任何一行数据,应用层路由配置热更新即可
  • ShardingSphere 中对应配置是 sharding-algorithmMOD 类型 + sharding-count: 1024,再配 actual-data-nodes: ds${0..3}.t_order_${0..255}
  • 一致性哈希(如 ketama)也能缓解迁移量,但不如预分片可控——它不保证绝对均匀,小集群下仍可能出现某节点多扛 20% 流量

按时间 RANGE 分表看似合理,实则暗藏 IO 火山

看到订单表按月建 order_202501order_202502,第一反应是“好维护”,但生产环境里,这种分法会让最新那张表成为 IO 灾难中心。

  • 所有新订单 INSERT 都打向 order_202602,单表写入 QPS 过万,磁盘随机写 + binlog 写 + 二级索引更新全压在一块 SSD 上,延迟飙升
  • 冷热分离失效:历史表虽不常查,但归档、备份、统计任务仍需扫全表,运维脚本若没按分表粒度并行,一次 select COUNT(*) 就锁住整个库
  • 折中做法是「RANGE + 哈希混合」:先按年份分库(order_db_2025order_db_2026),再在每个库里用 user_id % 8 分表——既隔离年份间压力,又打散单月写入
  • 绝对别让时间字段单独承担分片职责;它适合做二级路由条件(如查“2026 年 1 月北京用户订单”时,先定库再定表),而非一级分片键

跨分片查询不是不能做,而是得提前设计兜底路径

当产品突然提需求:“查近 7 天所有未发货订单”,而你的分片键是 user_id,这条路就注定要扫全部分片——这不是设计失败,是业务现实,关键是怎么扛住。

  • 第一响应永远是拒绝全量扫描:在 ShardingSphere 里配置 sql-show: true + 监控 sharding-sphere.log,一旦发现 Broadcast SQL 警告,立刻拦截并推动改需求
  • 真绕不开时,用异步宽表补位:通过 Binlog 订阅(如 Canal)把订单数据实时同步到 elasticsearch,查“最近 7 天未发货”走 ES 聚合,延迟控制在秒级
  • 禁止在应用层手动 for 循环查所有分片再合并:网络 RTT 放大、内存暴涨、任意一个分片超时就拖垮整条链路
  • 字典类小表(如 order_status)必须全局复制:每个物理库都存一份,避免 JOIN 时跨库,ShardingSphere 支持 broadcast-tables 配置自动同步 DML

分片键选对只是起点,真正的麻烦藏在数据增长曲线和业务需求突变里——比如某天突然要按手机号查用户,而你当初只按 user_id 分片,这时候补索引、建冗余表、改路由逻辑,成本远高于初期多想一步。

text=ZqhQzanResources