应选 user_id 作为分片键,因90%查询带 user_id 查历史订单;若同时高频查 tenant_id,可选组合键 tenant_id:user_id,并用 fnv 或 maphash 哈希加虚拟节点映射权重。

分片键选 user_id 还是 order_id?看查询模式,不是看主键
权重分片本质是把“流量”按比例切到不同库表,但切得再均匀,查不出来也是白搭。比如订单服务里,90% 的请求带 user_id 查历史订单,却用 order_id 做分片键——结果每次查都要扫所有分片,权重失去意义。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 先统计慢查询日志里高频出现的
WHERE条件字段,优先选它当分片键 - 如果业务同时高频查
user_id和tenant_id,考虑组合键(如tenant_id:user_id),但注意 Go 里哈希时要加固定分隔符,避免"12:3"和"1:23"碰撞 - 别为了“看起来均匀”硬选 UUID 或自增 ID——它们在范围查询、索引合并、跨分片 JOIN 上全是坑
hashMod 函数必须自己写,别直接用 sum([]byte(s)) % n
Go 标准库没提供带权重的哈希分片函数,而网上随手抄的简单求和取模,会导致数据倾斜严重:字符串内容相似时哈希值扎堆,比如大量 "user_1001"、"user_1002" 在模 8 下全落在第 1 个分片。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
hash/fnv或hash/maphash(Go 1.22+)做基础哈希,再映射到权重区间 - 权重映射别用线性叠加,改用“虚拟节点”或“一致性哈希 + 权重缩放”:比如分片 A 权重 3,B 权重 1,就让 A 占 75% 的哈希环空间
- 测试时用真实样本跑 10 万次,检查各分片记录数标准差是否
分库后 SELECT COUNT(*) 怎么不拖垮服务?
微服务里一个分页接口背后调三次 COUNT(*),每个库都扫全表,延迟直接上秒级。权重分片本身不解决聚合问题,反而让跨分片统计更难。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 业务能接受近似值?用
EXPLAIN拿rows估算,Go 里汇总后加权平均(误差通常 - 必须精确?把计数下沉到写路径:每次插入/删除,用 Redis
INCRBY更新对应分片的count:user_status:active,查时GET汇总 - 别在事务里做跨库
COUNT——MySQL XA 太重,且 Go 的database/sql默认不支持多 DB 事务协调
Go 连接池配 SetMaxOpenConns 时,权重高的库要单独调
默认所有分片共用同一套连接池参数,但权重 60% 的主库和权重 5% 的冷备库,如果都设 MaxOpenConns=20,前者可能排队,后者长期空闲——连接数不是按分片均分,而是按负载比例配。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 为每个分片实例单独初始化
*sql.DB,各自调SetMaxOpenConns,值 ≈ 总连接数 × 权重 × 1.2(留缓冲) - 监控
sql.DB.Stats().WaitCount,某分片持续 > 0 就说明连接不够,别只看全局指标 - 别复用同一个
sql.DB实例连多个库——Go 的连接池不识别库名,会把不同库的连接混在一起,导致路由错乱
权重分片最难的不是算哈希,是让每个分片的“水位”真正匹配它的权重:连接池、慢查阈值、告警规则、备份策略,全得跟着权重动态调整。漏掉任意一环,高权重分片早晚会成为单点瓶颈。










