分库分表是单表优化、读写分离、索引与SQL重构全部失效后的最后手段;需满足行数超1500万且查询性能差、锁等待严重、QPS高导致资源瓶颈、备份超时等条件之一才启动。

分库分表不是性能问题的第一解法,而是单表优化、读写分离、索引与SQL重构全部失效后的最后手段。 千万级大表在多数场景下,靠合理索引+查询重写+分区表就能扛住,盲目拆分反而引入分布式事务、跨库JOIN、全局ID、数据一致性等一连串新问题。
什么时候必须分库分表?
满足以下任意一条,才真正需要启动分库分表:
- 单表行数持续 > 1500 万,且
EXPLAIN显示高频查询仍走type=all或Extra=Using filesort/Using temporary,即使已建覆盖索引 - 单库
SHOW PROCESSLIST中长期存在大量Locked或Updating状态,innodb_row_lock_time_avg> 50ms - QPS 稳定超过 4000,但 CPU 使用率 > 85%、IO wait > 40%,且读写分离后从库延迟 > 30s 无法收敛
- 备份单表耗时 > 45 分钟,或
mysqldump导出失败(OOM 或超时)
水平分表:按ID取模还是按时间范围?
选错分片键,等于提前埋雷。ID取模(如 user_id % 8)适合点查多、无范围扫描的场景;但订单类业务若用ID分片,就无法高效查“某个月所有订单”——必须扫全部8个表。
时间范围(如 YEAR(create_time)*100 + MONTH(create_time))天然支持归档和冷热分离,但会导致新数据集中在最新分片,产生热点。
更稳妥的做法是组合使用:
- 主分片键用
user_id(保障用户维度查询不跨片) - 二级分区用
RANGE COLUMNS(create_time)(按月自动归档历史数据) - 避免用
UUID或MD5做分片键——它们打散太均匀,导致每个查询都跨片,且无法排序/范围裁剪
分库分表中间件 vs 手动路由:别被“轻量”骗了
ShardingSphere-JDBC 这类 JDBC 层中间件看似“零改造”,实则要求你手动处理 ORDER BY、GROUP BY、子查询、UNION 的聚合逻辑;而 MyCat 这类代理层又引入单点故障和连接池瓶颈。
小团队建议手动路由,但只做两件事:
- 在 DAO 层封装
getOrderTable(user_id)函数,返回类似orders_001的物理表名 - 所有 INSERT/UPDATE/SELECT 都显式拼接表名,不依赖 ORM 的自动映射(Hibernate/JPA 的
@Table注解会失效) - 放弃跨分片事务——用本地消息表 + 最终一致性替代
X/A,否则 TCC 或 Saga 成本远高于拆分收益
最容易被忽略的三件事
落地后最常崩的不是分片逻辑,而是这些细节:
-
auto_increment主键在分库后失效,必须改用雪花算法(Snowflake)或数据库号段模式生成全局唯一ID - 原
SELECT COUNT(*) FROM orders变成 8 次查询再求和,但ORDER BY create_time LIMIT 20不能简单合并结果——需各分片取前 160 条,内存中归并排序 - 备份策略必须从“全库 mysqldump”变成“逐库逐表并发导出”,否则一个分片慢就卡死整个流程


















