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

分片键选错,数据刚上线就变热点
分片键不是随便挑个 id 或 created_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-algorithm用MOD类型 +sharding-count: 1024,再配actual-data-nodes: ds${0..3}.t_order_${0..255} - 一致性哈希(如
ketama)也能缓解迁移量,但不如预分片可控——它不保证绝对均匀,小集群下仍可能出现某节点多扛 20% 流量
按时间 RANGE 分表看似合理,实则暗藏 IO 火山
看到订单表按月建 order_202501、order_202502,第一反应是“好维护”,但生产环境里,这种分法会让最新那张表成为 IO 灾难中心。
- 所有新订单 INSERT 都打向
order_202602,单表写入 QPS 过万,磁盘随机写 + binlog 写 + 二级索引更新全压在一块 SSD 上,延迟飙升 - 冷热分离失效:历史表虽不常查,但归档、备份、统计任务仍需扫全表,运维脚本若没按分表粒度并行,一次
select COUNT(*)就锁住整个库 - 折中做法是「RANGE + 哈希混合」:先按年份分库(
order_db_2025、order_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 分片,这时候补索引、建冗余表、改路由逻辑,成本远高于初期多想一步。