分片键应选业务主路径高频且分布均匀的字段,如订单系统用xxhash(user_id)取模;避免user_id、city等易倾斜字段,需结合二级分片支持时间范围查询。

sharding_key 怎么选才不会导致热点
分片键选错,查得再快也白搭——它直接决定数据在各节点是否均匀、查询能否路由到单节点。常见错误是拿 user_id 当默认分片键,结果发现 20% 的用户贡献了 80% 的读写(比如网红账号),所有请求全打到同一台库上。
实际选法得看业务主路径:
- 如果是订单系统,
order_id自增容易倾斜,改用user_id % shard_count或更稳的xxhash(user_id)再取模 - 如果要支持按时间范围查(如“查某天所有订单”),得加二级分片策略,不能只靠一个 key;否则就得扫全库
- 避免用含业务语义的字段(比如
city、status)——值域太小、分布不均,shard_count = 16时可能 12 个分片全是空的
如何让 MySQL 连接池不拖垮中间件
C++ 写中间件最常崩在连接管理:每个分片维护一套连接池,但没设上限或超时,一来大促流量,epoll_wait 没反应,二来 MySQL 主动断连后连接卡死,后续请求全 hang 住。
关键控制点:
立即学习“C++免费学习笔记(深入)”;
- 每个分片的连接池必须独立配置
max_idle_conn和max_active_conn,别共用全局池——不同分片负载差异大,统一限流等于给慢节点开绿灯 - 连接必须带
SO_KEEPALIVE+ 自定义心跳(比如每 30s 发PING),MySQL 默认 wait_timeout=28800 秒,但云数据库可能缩到 60 秒,不探测就会积攒大量 stale conn - 不要复用
mysql_real_connect返回的MYSQL*跨线程——C++ 里它不是线程安全的,要用mysql_thread_init+ 线程局部存储
SQL 解析和路由为什么不能靠正则
用 std::regex 匹配 SELECT * FROM users WHERE id = ? 看似简单,实际线上一跑就出事:遇到注释、子查询、引号嵌套、大小写混用,正则立刻漏判或误判,把跨分片查询当成单机查询,返回错误结果。
靠谱做法只有两个:
- 轻量级场景用现成 parser,比如
libsqlparser(支持 MySQL 语法子集),解析出table_name和where_conditionAST 节点,再递归提取等值条件中的变量名 - 复杂 SQL(含 JOIN / UNION)直接标记为
unsupported并报错ERR_SHARDING_UNSUPPORTED_SQL,别硬路由——中间件不是数据库,没必要兼容全部语法 - 注意参数绑定:预处理语句里
?的位置和类型必须和解析后的 AST 对齐,否则WHERE user_id = ?可能被错判成无分片条件
事务怎么跨分片还不丢一致性
真要跨分片事务,别碰两阶段提交(2PC)——C++ 里实现 prepare/commit/rollback 状态机+日志持久化+超时恢复,半年都调不完。生产环境基本只用两种方案:
- 业务层拆解:把“转账”拆成“扣 A 钱”和“加 B 钱”两个幂等操作,中间用消息队列兜底,中间件只保证单分片事务 ACID
- 强一致退让:限定事务只能发生在同一分片内,中间件在解析时检查所有
FROM和JOIN表是否属于同一个shard_id,否则拒绝执行并返回ERR_CROSS_SHARD_TRANSACTION - 切记:不要在中间件里做 XA,MySQL 的
XID在 C++ 客户端不透明,xa_start后无法透传到后端连接,最后要么锁表,要么死锁
最难的从来不是代码怎么写,而是哪些事坚决不做——比如动态加节点时的数据迁移、分布式唯一 ID 生成、慢查询自动归集,这些都该交给专用组件,中间件守住路由、连接、协议转换这三件事就够了。



















