单表超1000万行、qps超1万且索引优化和读写分离无效时才需分库分表;垂直分库适用于模块边界清晰的系统,水平分表须锁定高频稳定均匀的分片键(如user_id),避免status等倾斜字段。

单表超1000万行、QPS超1万,且索引优化和读写分离已无效时,才需要考虑分库分表;否则加从库、调参数、改SQL更简单可靠。
垂直分库适合业务模块清晰的系统
如果你的系统天然有明确边界——比如电商里用户、订单、商品、支付四个模块数据基本不跨库JOIN,各自读写流量独立,那就优先垂直分库。user_db、order_db、product_db这种拆法落地快、运维成本低,还能按模块单独扩容。
常见错误是强行按字段“垂直分表”,比如把 user 表拆成 user_profile 和 user_setting,但业务代码仍频繁连查两表——这反而增加网络开销和事务复杂度,没解决根本问题。
- ✅ 正确场景:各模块数据库连接池独立、微服务已按域拆分、DBA能为不同库设置差异化备份策略
- ❌ 错误信号:每次下单都要查用户余额(跨
user_db和order_db),又没引入分布式事务或最终一致性补偿机制 - ⚠️ 注意:
schema.xml(如 Mycat)中垂直分库通常不配rule,但必须确保应用层路由逻辑不漏写库名
水平分表必须先锁定分片键
单表撑不住了,比如 order 表已达 2500 万行,查询慢、写入锁等待高,这时才上水平分表。但第一步不是选工具,而是找那个**高频、稳定、不可变、分布均匀**的字段当分片键。
典型反例:create_time 按月分表,结果促销日订单暴增,某几张表瞬间成为热点;或者用 uuid 哈希,但 MySQL 的 UUID_SHORT() 或字符串 uuid 导致哈希倾斜,部分分片负载高出 3 倍。
- ✅ 推荐分片键:
user_id(整型、业务主键、访问频次高)、order_no(若含时间+序列段,可兼顾范围查询) - ❌ 避免分片键:
status(只有 3–5 个值,严重倾斜)、city_code(头部城市占 70% 流量) - ⚠️ 关键检查:执行
SELECT COUNT(*) FROM order GROUP BY user_id % 4;,确认余数分布是否接近 1:1:1:1
水平分库比水平分表多一层网络与事务代价
水平分库本质是把分表再跨物理实例——比如 order_001 到 shard-1 服务器,order_002 到 shard-2。它能突破单机磁盘和连接数上限,但代价明显:
- 跨库
JOIN基本不可行,除非用Federated引擎(不推荐生产)或应用层组装 -
XA分布式事务性能差、可用性低,99.9% 场景应改用本地事务 + 最终一致性(如发 MQ 补单) - 运维复杂度翻倍:备份要串行跑多个实例,慢查询日志分散在不同机器,
pt-query-digest得分别分析
真正需要水平分库的信号只有两个:单机磁盘持续 >85%,或 max_connections 经常打满且无法再调高。
别跳过中间件选型的真实约束
你不会手写分库分表路由逻辑,所以实际落地绕不开中间件。但不同方案对 SQL 兼容性、运维能力、团队技能要求差异极大:
-
Mycat:支持SELECT * FROM order WHERE user_id = ?自动路由,但GROUP BY跨分片会拉全量数据在内存聚合,容易 OOM -
ShardingSphere-JDBC:以 Jar 包嵌入应用,无额外运维节点,但升级需发版,且UNION ALL子查询可能被误判为不支持 - 自研代理层:可控性强,但你要自己实现连接池管理、故障转移、分片元数据同步——多数团队低估了这部分工作量
最容易被忽略的一点:所有中间件都依赖分片键出现在 WHERE 条件里。如果业务代码大量使用 SELECT * FROM order WHERE status = 'paid',那无论选哪个方案,都会退化成广播查询,性能比单库还差。











