结果导向 · 分片策略 / 读写分离 / 扩容 / 跨库问题 / 分布式 ID
ShardingSphere 是什么(一句话):Apache 顶级项目,一套 JDBC 增强层,让你的 Java 应用透明地操作多个数据库实例——你写 SQL 像操作单库一样,它自动帮你路由、改写、合并结果。不需要改业务代码。
什么时候需要分库分表(信号清单):
| 信号 | 说明 | 阈值参考 |
|---|---|---|
| 单表数据量过大 | MySQL 单表超过一定行数,B+ 树层级变深,查询变慢 | 单表 > 1000 万行 / 单表 > 50GB |
| 单库写 QPS 过高 | MySQL 单实例写能力有上限,主从延迟拉大 | 写 QPS > 5000 / 主从延迟 > 1s |
| 单库磁盘吃紧 | 数据增长快,磁盘扩容跟不上 | 磁盘使用率 > 70% |
| 单库连接数打满 | 应用多、连接池大,单库 max_connections 扛不住 | 连接数 > 2000 |
| 慢查询比例上升 | 索引优化已到极限,还是慢 | 慢查询占比 > 5% |
定位:ShardingSphere 是一个 JDBC 增强层(不是数据库,不是中间件进程)。你的 Java 应用引入它的 jar 包后,DataSource 由它接管,所有 SQL 都先过它一遍——它根据分片规则把 SQL 路由到正确的库和表,执行后把多个库的结果合并回来。对业务代码来说,就像操作一个单库。
SELECT * FROM order WHERE id=123 → ShardingSphere 根据分片规则算出 id=123 在 ds1 → 只查 ds1 → 返回结果核心能力全景(6 大块):
JdbcTemplate、MyBatis、JPA 该怎么用还怎么用。SQL 里的表名还是 order(逻辑表),ShardingSphere 自动改写成 order_0 order_1 等真实表。这是它和自研分库分表最大的区别——自研要改代码,它不用。
分库分表的方案主要有:ShardingSphere、MyCat(老牌)、Vitess(Google 出品)、TDDL(阿里自用)、自研。结果导向直接看下表:
| 维度 | ShardingSphere-JDBC | ShardingSphere-Proxy | MyCat | Vitess | 自研 |
|---|---|---|---|---|---|
| 类型 | 嵌入式 jar | 代理进程 | 代理进程 | 代理进程 | 应用层 |
| 性能 | ✅ 最好(无额外跳转) | ⚠️ 多一跳网络 | ⚠️ 多一跳网络 | ⚠️ 多一跳网络 | ✅ 最好 |
| 语言支持 | 仅 Java | ✅ 多语言(MySQL 协议) | ✅ 多语言 | ✅ 多语言 | 看实现 |
| 维护状态 | ✅ Apache 顶级项目,活跃 | ✅ 同上 | ❌ 基本停更 | ✅ 活跃(CNCF) | 自己维护 |
| 运维成本 | ✅ 最低(无额外进程) | ⚠️ 要维护 Proxy 集群 | ⚠️ 要维护 MyCat | ⚠️ 复杂,要 etcd | ❌ 最高 |
| 什么时候选它 | ✅ Java 项目首选 | 多语言/DBA 统一管理 | ❌ 不推荐(过时) | K8s 环境/超大规模 | 特殊需求/学习 |
分库分表不是随便拆,要先想清楚两个问题:①按什么字段拆(分片键)?②按什么规则拆(分片算法)?这两个选错,后面全是坑。
分片键就是"按哪个字段拆数据"。选错了,所有查询都会变成全库广播(查所有库再合并),性能灾难。
| 分片键 | 适合场景 | 优点 | 坑 |
|---|---|---|---|
| user_id(用户ID) | 订单/消息/购物车等"用户维度"数据 | ✅ 90% 查询带 user_id,单库命中 | 某些用户数据量异常大时倾斜 |
| order_id(订单ID) | 按订单查询为主 | ✅ 订单查询精准路由 | ❌ 按 user_id 查订单时广播 |
| create_time(创建时间) | 日志/流水类按时间归档 | ✅ 时间范围查询精准 | ❌ 热点:最新数据全在一个分片 |
| id(主键) | 无明确业务维度的数据 | ✅ 分布均匀 | ❌ 业务查询几乎不带 id,全广播 |
| 算法 | 规则 | 例子 | 适合 |
|---|---|---|---|
| 取模 | 分片键 % 分片数 | user_id % 4 = 0/1/2/3 | 数据分布均匀,但扩容要迁移大量数据 |
| 范围 | 按区间分片 | id 1-1000万→ds0,1000万-2000万→ds1 | 扩容简单(新加区间),但热点集中 |
| 一致性 hash | hash 环 | hash(user_id) 映射到环上 | ✅ 扩容只迁移一部分数据(推荐) |
| Hint | 代码里手动指定走哪个库 | HintManager.set("ds1") | 特殊场景,灵活但侵入业务 |
t_order 映射到 16 张物理表(ds0-3 各 4 张);③按 user_id 取模分库分表(user_id % 4 决定库,再 % 4 决定表);④用雪花算法自动生成 order_id。配完后,业务代码里写 SELECT * FROM t_order WHERE user_id=123,ShardingSphere 自动路由。
INSERT/UPDATE/DELETE 走主库,SELECT 走从库。但有个坑:事务内的 SELECT 也会走主库(因为从库有延迟,刚写完马上读可能读不到)。配 transactionalReadQueryStrategy: PRIMARY 就是干这个的,这是必须配的,否则"写完读不到"的 bug 会让你排查到怀疑人生。
WHERE user_id=? 精准路由一个库;不带 user_id 的查询会广播到所有库,性能差 4 倍以上(4 个库的情况)。后台管理类的全量查询(比如"查所有待处理订单")建议走独立的非分片库(用 Canal 同步一份数据),不要走分片库。
背景:电商订单表 3 年积累 8000 万行,单表查询慢、单库磁盘吃紧。按 user_id 分到 4 个库,每个库 4 张表,共 16 张物理表,单表降到 500 万行,查询快了 10 倍。
数据分布情况(分片后各库表数据量):
背景:订单系统读写分离后,用户反馈"下单成功后立刻看订单列表,有时看不到"。典型主从延迟问题——写主库后从库还没同步,读从库读不到。
背景:业务增长,4 个库扛不住了(单库 QPS 5000 接近上限),要扩到 8 个库。取模算法扩容的痛点:user_id % 4 和 user_id % 8 结果不同,几乎所有数据都要重新分布。
现象:分库分表后,运营后台报表查询 SELECT o.*, u.name FROM t_order o JOIN t_user u ON o.user_id=u.id 直接报错。订单表按 user_id 分了 4 库,用户表在另一个库,跨库 JOIN 不支持。
原因:ShardingSphere 不支持跨库 JOIN(跨同一个分片键的可以,跨不同库的不行)。分库后订单和用户不在一个库,JOIN 就废了。
解法(3 选 1):
① 绑定表(推荐):把 user 表也按 user_id 分片,保证 user 和 order 在同一个库,JOIN 就能正常走。bindingTables: [t_order, t_user]
② 广播表:小表(如字典表、配置表)在每个库都存一份,JOIN 时本地查。适合数据量小且不怎么变的表。
③ 应用层 JOIN:先查订单,再批量查用户,代码里合并。性能差,适合低频后台查询。
现象:分库分表后,SELECT * FROM t_order ORDER BY create_time LIMIT 100000, 10(查第 1 万页)直接超时。前几页很快,越往后越慢。
原因:分库分表后的分页是归并排序——ShardingSphere 要查所有分片的 LIMIT 0, 100010,然后在内存里合并再取 10 条。页越深,每个分片查的数据越多,4 个分片每个查 10 万行,内存爆了。
解法:①禁止深度分页,超过 100 页报错或提示"请缩小查询范围";②游标分页(WHERE create_time > last_time ORDER BY create_time LIMIT 10),每次记住上一页最后一条;③后台深度查询走 ES 或独立汇总库。
现象:下单操作要同时写 t_order(ds0)和 t_inventory(ds2),没配分布式事务。ds0 写成功后 ds2 写失败,结果订单创建了但库存没扣,超卖。
原因:默认 @Transactional 只能保证单库事务。跨库操作要么用 ShardingSphere 的分布式事务(XA/BASE),要么自己实现补偿。
解法:
现象:运营跑了条 SELECT COUNT(*) FROM t_order WHERE create_time > '2026-06-01',16 张表全查一遍,每张表 500 万行,单次查询耗时 40 秒,把 4 个库的连接池全占满,线上服务全部超时。
原因:不带分片键 user_id 的查询会全库广播,16 张表全表扫描。运营后台一个查询把生产搞挂。
解法:①后台统计类查询禁止走分片库,走 Canal 同步到 ES/汇总库查;②ShardingSphere 配置 sql-show: true 开发期就能发现广播 SQL;③代码审查:所有 SQL 必须带分片键,不带的不让上线。这是分库分表后最常见的线上事故。
单库时用 MySQL 自增 ID 就行,分库后每个库各自自增会冲突。3 种方案:
① 雪花算法(Snowflake,推荐):64 位 = 1 位符号 + 41 位时间戳 + 10 位机器 ID + 12 位序列号。ShardingSphere 内置支持,配 keyGeneratorName: snowflake 即可。优点:趋势递增、不依赖第三方、性能好。
② 数据库号段:用一张独立表分配 ID 段(一次发 1000 个),应用缓存本地用完再取。优点:简单;缺点:依赖 DB,重启丢一段。
③ UUID:不推荐——无序、占空间(36 字符)、索引性能差。
优先选 JDBC。理由:
• 性能:JDBC 是进程内调用,零网络开销;Proxy 多一跳网络,QPS 损失 10-20%。
• 运维:JDBC 是 jar 包,跟随应用部署,无额外进程;Proxy 要独立部署集群,还得高可用。
• 故障点:JDBC 挂了应用也挂了(一起的);Proxy 挂了所有应用都受影响(单点风险)。
什么时候选 Proxy:①多语言团队(Java + Python + Go 都要用);②DBA 要统一管理所有数据库连接;③不想改 Java 应用配置(Proxy 对应用透明)。
能,完全不用改。ShardingSphere 接管的是 DataSource 层,对上层 ORM 框架透明。MyBatis、JPA(Hibernate)、JdbcTemplate、Spring Data 全部正常用。
你写的 SQL 里表名还是逻辑表名 t_order,ShardingSphere 自动改写成真实表名 t_order_0。MyBatis 的 XML 映射、注解 SQL 一个字都不用改。这是 ShardingSphere 最大的优势——零侵入。
唯一要注意的是:某些 ShardingSphere 不支持的 SQL 语法(比如某些跨库子查询),会报错。这种情况要么改写 SQL,要么走非分片库。
这是个大工程,核心是"不停机平滑迁移"。标准流程:
① 搭建分片库:建好 4 个新库 16 张表,配好 ShardingSphere。
② 双写:应用改成同时写旧库和新分片库(旧库用旧逻辑,新库按分片规则写)。
③ 数据同步:用工具(DataX/Canal)把旧库存量数据按分片规则同步到新库。
④ 校验:对比新旧库数据一致性,有差异修复。
⑤ 切读:读流量灰度切到新分片库(10% → 50% → 100%)。
⑥ 下线旧库:停止双写,下线旧库。整个过程通常 1-2 周。
以下情况不要急着分:
① 单表 < 1000 万行:MySQL 单表 1000 万内优化好索引完全够用,分了反而增加复杂度。
② 没试过读写分离:先读写分离(读压力分摊到从库),能解决 70% 的性能问题,比分库分表简单 10 倍。
③ 没试过 Redis 缓存:热点数据放 Redis,DB 压力直接降一个量级。
④ 没归档冷数据:3 年前的历史订单归档到冷库,主表数据量直接砍半。
⑤ 团队没专职 DBA:分库分表运维复杂度高,没专人维护容易出事。分库分表是放大器——放大性能,也放大复杂度和故障风险。
1. 定位:JDBC 增强层,透明分库分表。业务代码不用改,配 YAML 就能用。Java 项目首选 JDBC 模式。
2. 分片键是命脉:选查询里"几乎每次都带"的字段。查询不带分片键 = 全库广播 = 性能灾难。取模分布均匀但扩容痛,一致性 hash 扩容友好。
3. 能不分就不分:分库分表是最后手段。先穷尽索引/读写分离/缓存/归档/硬件升级。分了之后跨库 JOIN、深度分页、分布式事务全是坑,运维成本翻倍。
相关专题:MySQL 场景决策 · Redis 专题 · 消息队列决策
全栈资料库 · ShardingSphere 分库分表实战