分库分表是单库单表明确出现性能、存储或运维瓶颈时的被迫选择,非万能银弹;当单表超1000万行、磁盘超15gb或ddl/备份已严重阻塞业务时才需考虑,且应优先尝试索引优化、读写分离等低成本方案。

分库分表不是必选项,而是被逼出来的选择;只要单表没超 1000 万行、磁盘占用没超 15GB、日常 DDL 和备份还能忍,就先别动。
什么时候该考虑手动分表而不是上 ShardingSphere
手动分表适合你清楚数据流向、写入路径单一、查询绝大多数带分片键(比如 user_id)、且不打算频繁扩缩容的场景。ShardingSphere 会帮你路由、重写 SQL、管理分布式事务,但代价是引入新组件、配置复杂、升级踩坑多、排查慢查询得看两层日志(应用 + 代理)。
- 手动分表:你控制建表、插入逻辑、查询路由,SQL 简单透明,运维负担低,但所有跨分片操作(如
count(*)、ORDER BY ... LIMIT)都得自己拼 - ShardingSphere-JDBC:嵌在应用里,无代理层,轻量,但依赖 Java 生态,非 Java 服务无法复用
- ShardingSphere-Proxy:独立进程,支持多语言,但网络跳转多一层,连接池和事务行为与直连 MySQL 不完全一致
- 真正上线前,务必用真实流量压测
INSERT和SELECT WHERE user_id = ?路由性能——Proxy 在高并发下容易成为瓶颈
水平分表必须避开的三个硬伤
按 user_id % 4 拆成 orders_0~orders_3 看似简单,但实际运行中会暴露三类典型问题:
-
非分片键查询失效:查
SELECT * FROM orders WHERE order_no = 'xxx',你得在 4 张表里全扫一遍,或者额外建order_no → user_id映射表,否则就是 O(n) 查询 -
扩容成本极高:从 4 分片扩到 8 分片,
user_id % 4 == 0的数据里有一半要迁到新表,没有停机窗口几乎做不到原子迁移 -
自增主键冲突:各分表都用
AUTO_INCREMENT,不同表可能生成相同order_id;必须改用全局唯一方案,比如Snowflake或REPLACE INTO id_generator+LAST_INSERT_ID()
ShardingSphere 中 sharding-column 配置的真实约束
你在 application.yml 里写的 sharding-column: user_id 并不是“只要 SQL 里出现 user_id 就能自动路由”,它只对以下情况生效:
- WHERE 条件中为等值查询:
WHERE user_id = 123✅;WHERE user_id IN (123, 456)✅;WHERE user_id > 100❌(默认走广播) - INSERT 语句中必须显式提供
user_id值,不能为NULL或缺失,否则抛ShardingSphereSQLException - JOIN 查询中,只有当两张表的分片键完全一致且类型兼容时,才可能做绑定表(
binding-tables)优化;否则默认拆成多次单表查询再内存合并 - 如果你用 MyBatis-Plus 的
lambdaQuery().eq(Order::getUserId, 123),它能正常路由;但用lambdaQuery().like(Order::getOrderNo, "xxx"),就会触发全分片扫描
最容易被忽略的冷启动陷阱
上线第一周最危险的不是性能,而是数据错位和索引遗漏。你建了 orders_0~orders_3 四张表,但只给 orders_0 加了 INDEX idx_user_id,其他三张漏了——那 75% 的查询会走全表扫描,监控却只显示“平均响应时间正常”,因为慢请求被均摊掩盖了。
务必在部署脚本里固化检查项:SHOW CREATE TABLE orders_0 对比结构、SELECT COUNT(*) FROM information_schema.STATISTICS WHERE table_name LIKE 'orders_%' AND index_name = 'idx_user_id' 确认索引全覆盖、用 pt-table-checksum 抽样校验分片间数据一致性。分库分表不是建完表就结束,而是把“结构同步”变成持续动作。











