Spring Boot 3.5 整合 Apache ShardingSphere 5.5.3 深度实践
欢迎你来读这篇博客,这篇博客主要是关于 Spring Boot 3.5 整合 Apache ShardingSphere 5.5.3 的工程化实践。
这篇文章不会只停留在“配置两个库、插一条数据”的入门层面,而是尽量把 ShardingSphere 的定位、架构、功能边界、Spring Boot 3.5
接入方式、分库分表、读写分离、分布式事务、加密、脱敏、影子库、Proxy、DistSQL、数据迁移、可观察性和生产避坑都串起来。
如果你只想复制一份可跑配置,可以直接看 正文 -> 5. Spring Boot 3.5 整合 ShardingSphere-JDBC。
序言
提到分库分表,很多人的第一反应还是“拆表、取模、路由、改 SQL”。这当然是核心,但 ShardingSphere 早就不只是一个分库分表工具。
Apache ShardingSphere 官方把它定位为 Database Plus,也就是构建在异构数据库之上的增强层。它不重新发明一个数据库,而是尽量复用
MySQL、PostgreSQL、openGauss、SQL Server 等已有数据库的存储能力,然后在其上提供统一访问、分布式
SQL、分片、读写分离、数据加密、数据脱敏、影子库、数据迁移、可观察性等能力。
在 Spring Boot 项目里,ShardingSphere 最常用的是 ShardingSphere-JDBC。它本质上是一个增强版 JDBC Driver,应用依旧拿到的是一个DataSource,MyBatis、JPA、JdbcTemplate 都可以接在这个数据源上。你写的是逻辑 SQL,ShardingSphere
根据规则解析、路由、改写、执行、归并,最后把结果返回给应用。
有一点先说清楚:ShardingSphere
很强,但它不是万能胶。分库分表会让系统复杂度明显上升,一旦用了,就要认真面对数据建模、分片键选择、跨库查询、全路由、分布式事务、扩容迁移、监控排查这些问题。它不是“加个依赖解决数据库性能问题”,更像是你给数据库系统加了一层交通枢纽。交通枢纽能提效,也会带来新的规则。
本文解决什么问题
很多 ShardingSphere 教程的问题并不是配置不能启动,而是只展示了“最顺利的一条路径”:建两个库、按 ID 取模、插入两条记录,然后宣布分库分表完成。真正进入生产后,问题才会集中出现:
- 为什么同一条逻辑 SQL 会展开成几十条真实 SQL?
- 为什么加了分片后,
COUNT、ORDER BY、GROUP BY和深分页突然变慢? - 为什么明明用了
@Transactional,跨库写入仍然可能出现部分成功? - 为什么应用只有 20 个连接,最终却把数据库打出了上千个连接?
- 为什么扩容不是把
${0..1}改成${0..3}就结束? - 为什么同样的 YAML 在 5.3、5.4、5.5 中可能出现属性名或行为差异?
- 如何验证一条 SQL 的真实路由、归并方式和连接消耗,而不是靠猜?
因此,本文以“设计—接入—验证—上线—扩容—排障”为主线。你不仅会得到一份 Spring Boot 3.5 + ShardingSphere 5.5.3 的完整配置,还会理解配置背后的代价。
阅读路线
flowchart TD
A[先判断是否真的需要分库分表] --> B[理解逻辑表与真实节点]
B --> C[选择分片键与分片算法]
C --> D[完成 Spring Boot 3.5 接入]
D --> E[验证精确路由与全路由]
E --> F[处理事务与读一致性]
F --> G[建立监控和压测基线]
G --> H[设计迁移、扩容和回滚]
A -.不需要.-> I[优先索引、归档、缓存、读写分离]
建议第一次阅读按顺序看;已经接入过 ShardingSphere 的读者,可以重点阅读第 6、13、16、17、20、26~32 节。
版本与资料使用说明
本文的配置语法以 Apache ShardingSphere 5.5.3 为准,不混用 4.x 的 spring.shardingsphere.* Starter 配置,也不把 current 文档中未来版本的变化直接套回 5.5.3。尤其是数据源 URL 属性、单表规则、事务支持、Proxy 配置与 DistSQL 语法,必须以项目实际锁定版本为准。
本文在原有博客内容上做增量扩写,保留已有章节与示例,并新增:
- SQL 解析、绑定、路由、改写、执行、归并的完整内核链路;
- 可直接启动的 Docker Compose、初始化 SQL 与工程目录;
- MyBatis、JdbcTemplate、集成测试和路由断言;
- 分片容量规划、虚拟槽位与扩容策略;
- 读写一致性、分布式事务边界和 Outbox 最终一致性;
- Proxy 高可用、DistSQL 变更流程和数据迁移 Runbook;
- 监控指标、日志规范、压测方法与生产验收清单。
正文
1. 版本选择与 Spring Boot 3.5 适配结论
截至本文撰写时,Apache ShardingSphere 官方下载页显示当前版本为 5.5.3,发布日期为 2026-03-01。本文以:
1 | |
作为示例基线。
Spring Boot 3.x 已经切换到 Jakarta EE 9+,最低 Java 基线也发生了变化。ShardingSphere 5.5.x 对 Spring Boot 3
的推荐接入方式,不再是老版本常见的 shardingsphere-jdbc-core-spring-boot-starter,而是:
1 | |
也就是说,应用只需要把 ShardingSphere 当成 JDBC Driver 使用。
1.1 旧 starter 不建议再用
历史文章里经常能看到这些依赖:
1 | |
新项目不要再优先照抄这些。Spring Boot 3.5 项目建议直接使用:
1 | |
然后通过 ShardingSphereDriver 加载 YAML 配置。
1.2 Spring Boot 3.5 下的事务提醒
官方文档明确提醒:ShardingSphere 的 XA 分布式事务在 Spring Boot OSS 3 / Jakarta EE 9+ 场景下尚未完全就绪。
所以 Spring Boot 3.5 项目里建议这样处理:
- 普通业务优先使用
LOCAL。 - 强一致跨库事务不要轻易承诺。
- 真正需要跨库强一致时,要单独压测和验证 XA / BASE 方案,不要只看配置能启动。
- 能通过业务设计规避跨库事务,就尽量规避。
- 订单、结算、库存这类场景可以考虑最终一致性、消息表、补偿任务、对账任务。
分库分表之后还想继续像单库事务一样随便写,这是很多事故的开始。数据库不会因为我们加了中间件就突然变温柔。
1.3 5.5.3 为什么值得单独说明
5.5.3 不是简单的修订号升级。它包含大量安全修复、SQL 兼容性改进、JDBC/Proxy/DistSQL 稳定性修复,并继续推进可插拔打包。对生产项目而言,升级价值主要体现在三个方面:
- 安全基线更完整:发布说明列出了多项 CVE 修复。生产环境不应长期停留在已知漏洞版本。
- 连接与资源管理修复:包括特定分布式事务和 JDBC 连接释放问题,直接关系到长时间运行后的连接泄漏风险。
- 兼容性和并发稳定性改进:涉及 PreparedStatement、DistSQL 并发执行、不同数据库协议等问题。
但升级不能只改 Maven 版本。至少需要回归:
1 | |
1.4 版本锁定与依赖收敛
建议在父 POM 中锁定 ShardingSphere 版本,并检查依赖树中是否混入旧模块:
1 | |
如果项目只使用统一聚合依赖,也可以直接指定:
1 | |
检查依赖:
1 | |
重点排查:
- 同时存在 4.x 与 5.x ShardingSphere 包;
- 业务组件偷偷引入旧 Starter;
- SnakeYAML 被强制降级;
- HikariCP 版本被第三方 BOM 覆盖;
- MySQL Connector/J 同时出现
mysql:mysql-connector-java和com.mysql:mysql-connector-j。
1.5 不同配置方式如何选择
| 配置方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JDBC Driver + YAML | 配置直观,Spring Boot 3 接入简单 | 静态配置变更通常需要重启 | 大多数 Java 服务 |
| Java API | 可编程、可动态组装 | 代码量大,配置治理成本高 | 框架封装、动态租户 |
| Proxy + YAML | 多语言透明接入 | 独立部署,增加网络跳转 | 中小规模 Proxy 集群 |
| Proxy + DistSQL | 在线管理、集中治理 | 变更权限与审计要求高 | 平台化数据库网关 |
| JDBC/Proxy + Cluster Mode | 元数据共享、支持治理 | 引入注册中心和一致性问题 | 多实例统一规则 |
对普通 Spring Boot 业务服务,优先使用 JDBC Driver + YAML。不要因为“动态”听起来高级,就把规则修改权限直接暴露给应用。
2. ShardingSphere 是什么
Apache ShardingSphere 是一个分布式 SQL 事务和查询引擎生态,它主要包含两个常用接入端:
ShardingSphere-JDBCShardingSphere-Proxy
也可以把二者混合使用。
2.1 ShardingSphere-JDBC
ShardingSphere-JDBC 是轻量级 Java 框架,在 Java JDBC 层提供增强能力。
特点:
- 以 Jar 包方式集成到应用内部。
- 应用直接连接真实数据库。
- 不需要单独部署 Proxy 服务。
- 性能损耗相对小。
- 适合 Java 项目,尤其是 Spring Boot、MyBatis、JPA、JdbcTemplate 项目。
- 配置跟随应用发布,适合应用自治。
适合场景:
- 只有 Java 技术栈。
- 希望接入成本低。
- 希望少部署一个中间件服务。
- 应用团队能管理分片规则。
- 对性能敏感。
2.2 ShardingSphere-Proxy
ShardingSphere-Proxy 是透明数据库代理。应用把它当成一个 MySQL / PostgreSQL / openGauss 服务连接。
特点:
- 独立部署。
- 对多语言友好,Java、Go、Python、PHP、Node.js 都能连。
- 配置和规则可以集中治理。
- 支持 DistSQL 动态管理规则。
- 更适合运维管控、异构语言、统一数据库网关场景。
适合场景:
- 多语言系统。
- 多业务系统共享一套分片规则。
- 希望 DBA 或平台团队集中管理规则。
- 需要 DistSQL、数据迁移、CDC、治理能力。
- 不希望每个应用都内嵌 ShardingSphere-JDBC。
2.3 Hybrid 混合架构
混合架构是指部分 Java 应用使用 ShardingSphere-JDBC,其他语言或外部系统通过 ShardingSphere-Proxy 访问同一套逻辑数据库。
这种模式适合大平台,但规则治理要非常谨慎,尤其要避免:
- JDBC 和 Proxy 的规则不一致。
- 应用本地 YAML 与治理中心配置冲突。
- 变更规则时只更新了一侧。
- 不同入口对同一张逻辑表的路由结果不同。
2.4 JDBC 与 Proxy 的请求链路差异
flowchart LR
subgraph JDBC模式
A1[Spring Boot 应用] --> A2[ShardingSphere-JDBC]
A2 --> A3[(MySQL ds_0)]
A2 --> A4[(MySQL ds_1)]
end
subgraph Proxy模式
B1[Java / Go / Python / BI] --> B2[ShardingSphere-Proxy 集群]
B2 --> B3[(MySQL ds_0)]
B2 --> B4[(MySQL ds_1)]
B2 <--> B5[(ZooKeeper / Etcd)]
end
JDBC 模式的“网络跳数”更少,但规则和依赖进入每个应用实例;Proxy 模式把计算和规则集中到了独立服务,却需要承担 Proxy 的容量、高可用、连接复用和协议兼容问题。
2.5 选型决策树
flowchart TD
A{是否多语言统一访问} -->|是| P[优先 Proxy]
A -->|否| B{Java 服务是否允许内嵌依赖}
B -->|否| P
B -->|是| C{是否要求规则集中在线变更}
C -->|强要求| P
C -->|弱要求| J[优先 JDBC]
P --> D{是否有独立 DBA/平台团队}
D -->|没有| E[谨慎:Proxy 运维成本可能更高]
D -->|有| F[建设 Proxy 高可用与 DistSQL 审计]
2.6 ShardingSphere 不负责什么
为了避免错误预期,需要明确它不等于:
- 自动把任意慢 SQL 变快;
- 自动选择正确分片键;
- 自动消除跨库事务;
- 自动把 OLTP 查询变成 OLAP 引擎;
- 自动解决数据库主从复制延迟;
- 自动完成无风险扩容;
- 自动保障业务幂等和数据对账;
- 自动替代数据库索引、备份、审计与权限系统。
ShardingSphere 提供的是数据库增强和统一 SQL 入口。业务模型错了,中间件通常只会让错误跑得更分布式。
3. 核心功能总览
ShardingSphere 5.5.3 常见能力可以按下面几类理解。
mindmap
root((Apache ShardingSphere))
接入端
ShardingSphere-JDBC
ShardingSphere-Proxy
Hybrid
数据分布
数据分片
读写分离
单表
广播表
绑定表
数据安全
数据加密
数据脱敏
SQL 审计
流量与测试
影子库
强制路由 Hint
流量治理
事务
LOCAL
XA
BASE
管控
DistSQL
元数据持久化
集群模式
规则治理
迁移与生态
数据迁移
CDC
SQL 联邦查询
可观察性
3.1 数据分片
数据分片就是把一张逻辑表拆到多个真实数据库、多个真实表里。
常见拆法:
- 只分表:
demo_ds.t_order_0、demo_ds.t_order_1 - 只分库:
demo_ds_0.t_order、demo_ds_1.t_order - 分库又分表:
demo_ds_${0..1}.t_order_${0..15}
3.2 读写分离
读写分离用于主从架构:
- 写 SQL 路由到主库。
- 读 SQL 路由到从库。
- 事务内读请求默认可以路由到主库,保证读己之写。
- 从库可以通过随机、轮询、权重等算法负载均衡。
3.3 分布式事务
ShardingSphere 提供:
LOCALXABASE
但是 Spring Boot 3.5 场景要特别注意 XA 的兼容性限制。不要拿分布式事务当默认方案,优先通过分片键设计让单次业务写入落到同一个物理库。
3.4 数据加密
应用读写逻辑列,例如 phone,真实数据库保存密文列,例如 phone_cipher。
ShardingSphere 自动完成:
- INSERT 时明文转密文。
- SELECT 时密文转明文。
- WHERE 查询时改写查询条件。
- 可配置辅助查询列、模糊查询列。
3.5 数据脱敏
数据脱敏更偏向查询结果保护,比如:
- 手机号:
138****1234 - 邮箱:
ma***@example.com - 密码:MD5 或固定掩码
加密是存储层保护,脱敏是展示层保护,二者不要混为一谈。
3.6 影子库
影子库用于生产压测隔离。带压测标记的 SQL 会路由到影子库,不污染生产库。
常见标记:
- SQL Hint:
/* SHARDINGSPHERE_HINT: SHADOW=true */ - 某个字段命中规则,比如
user_id = 1 - 某个 SQL 操作类型命中规则,比如 INSERT
3.7 DistSQL
DistSQL 是 ShardingSphere 的分布式 SQL 管理语言,主要用于 Proxy 场景。
它可以通过 SQL 方式完成:
- 注册存储单元。
- 创建分片规则。
- 查看规则。
- 修改规则。
- 导入导出配置。
- 管理迁移任务。
- 查看运行状态。
3.8 数据迁移与 CDC
ShardingSphere-Proxy 提供数据迁移能力,适合:
- 单库迁移到分库分表。
- 老库迁移到新库。
- 调整分片规则后的数据搬迁。
CDC 则用于捕获数据变更,适合数据同步、数据订阅、异构系统集成。
3.9 一条 SQL 在 ShardingSphere 内部经历什么
从应用视角看,只是一次 PreparedStatement.executeQuery();从内核视角看,至少包含以下阶段:
flowchart LR
A[逻辑 SQL] --> B[解析 Parse]
B --> C[绑定 Bind]
C --> D[路由 Route]
D --> E[改写 Rewrite]
E --> F[执行 Execute]
F --> G[归并 Merge]
G --> H[逻辑 ResultSet]
C -.依赖.-> M[(逻辑表/列/规则元数据)]
D -.输出.-> R[路由上下文]
E -.输出.-> U[真实执行单元]
各阶段职责:
| 阶段 | 输入 | 核心工作 | 常见风险 |
|---|---|---|---|
| 解析 | SQL 文本、参数 | 生成语法树和 SQLStatement | 方言、不支持语法、解析成本 |
| 绑定 | 语法树、元数据 | 解析表、列、别名和上下文 | 元数据不一致、列歧义 |
| 路由 | SQL 上下文、规则 | 计算目标库表 | 缺分片键、笛卡尔路由 |
| 改写 | 逻辑 SQL、路由结果 | 替换表名、补列、修正分页 | SQL 膨胀、参数重排 |
| 执行 | 真实 SQL 集合 | 获取连接、并发/串行执行 | 连接风暴、线程和内存压力 |
| 归并 | 多个 ResultSet | 排序、聚合、分页、去装饰 | 深分页、内存归并、慢消费 |
3.10 规则叠加顺序
一个逻辑数据库里可能同时启用分片、读写分离、加密、脱敏、影子库等规则。不要把它们理解成互相独立的“开关”,因为同一条 SQL 会经历规则链。
例如“分片 + 读写分离 + 影子库”的请求:
sequenceDiagram
participant App as 应用
participant SS as ShardingSphere
participant Shard as 分片规则
participant RW as 读写分离规则
participant Shadow as 影子规则
participant DB as 真实数据库
App->>SS: 逻辑 SQL + 参数 + 压测标记
SS->>Shard: 根据 user_id/order_id 计算分片
Shard-->>SS: ds_1.t_order_0
SS->>RW: 根据 SQL 类型和事务状态选主/从
RW-->>SS: ds_1_primary
SS->>Shadow: 判断是否命中影子算法
Shadow-->>SS: shadow_ds_1_primary
SS->>DB: 改写后的真实 SQL
DB-->>SS: ResultSet / update count
SS-->>App: 逻辑结果
规则越多,测试矩阵呈乘法增长。生产接入时应逐个启用并分别验收,不要一次把所有能力全开。
4. 核心概念
想把 ShardingSphere 用稳,先把这些概念吃透。
4.1 逻辑库
应用看到的是逻辑库,例如:
1 | |
它不一定对应真实数据库,而是 ShardingSphere 对外暴露的数据库名称。
4.2 真实库
真实库是实际数据库实例里的 schema,例如:
1 | |
4.3 逻辑表
应用 SQL 里写的表名:
1 | |
这里 t_order 是逻辑表。
4.4 真实表
数据库里真实存在的表:
1 | |
4.5 actualDataNodes
actualDataNodes 描述逻辑表对应哪些真实节点:
1 | |
展开后是:
1 | |
4.6 分片键
分片键是决定数据落点的字段,例如:
1 | |
分片键选择是分库分表最重要的设计之一。选错分片键,后面所有查询都会开始“环游世界”。
4.7 分片策略
常见策略:
standard:单分片键。complex:多分片键。hint:通过 HintManager 指定路由。none:不分片。
4.8 分片算法
常用算法:
INLINE:行表达式,例如ds_${user_id % 2}。MOD:取模。HASH_MOD:哈希取模。INTERVAL:按时间间隔。AUTO_INTERVAL:自动时间段。COMPLEX_INLINE:复合行表达式。HINT_INLINE:Hint 行表达式。CLASS_BASED:自定义 Java 类算法。
4.9 绑定表
绑定表用于避免关联查询时出现笛卡尔路由。
例如:
1 | |
如果二者都按 order_id 分片,可以配置为绑定表:
1 | |
这样关联查询时可以保持同路由。
4.10 广播表
广播表会在所有数据源中保存一份,适合小型字典表、配置表,例如:
1 | |
配置:
1 | |
4.11 单表
单表是被 ShardingSphere 管理,但不参与分片的表。
例如:
1 | |
ShardingSphere 5.4.0 之后单表加载方式有调整,建议显式配置单表,别指望它总是自动猜。
4.12 分布式主键
分库分表后,数据库自增主键容易重复,所以常用:
SNOWFLAKEUUID- 业务自定义 ID
ShardingSphere 内置 SNOWFLAKE 和 UUID,也可以自定义。
4.13 数据节点、分片策略和算法的关系
这三个概念最容易混淆:
1 | |
flowchart LR
A[SQL 条件 user_id=10] --> B[databaseStrategy.standard]
B --> C[提取 shardingColumn=user_id]
C --> D[调用 database_inline]
D --> E[计算 ds_0]
E --> F{是否属于 actualDataNodes}
F -->|是| G[生成真实执行单元]
F -->|否| H[路由异常]
actualDataNodes 是边界,算法不能返回边界之外的名字。自定义算法必须对返回值做存在性校验,避免悄悄路由到错误节点。
4.14 分片值条件对路由数量的影响
假设有 2 个库、每库 4 张表:
| SQL 条件 | 可能路由数 | 说明 |
|---|---|---|
user_id = ? AND order_id = ? |
1 | 精确库 + 精确表 |
user_id IN (?, ?) + 精确 order_id |
1~2 | 命中一个或两个库 |
精确 user_id,无 order_id |
4 | 单库全表 |
无 user_id,精确 order_id |
2 | 所有库中的同尾号表 |
| 两个分片键都没有 | 8 | 全库全表 |
| 分片键范围查询 | 取决于算法 | INLINE 通常无法精确裁剪 |
所以“SQL 能执行”不等于“路由合理”。代码评审时应把路由数量当作 SQL 的一部分。
4.15 绑定表为什么能避免笛卡尔路由
如果 t_order 与 t_order_item 都按照相同的 order_id 规则分表,配置绑定表后:
flowchart TD
A[t_order_0] --- B[t_order_item_0]
C[t_order_1] --- D[t_order_item_1]
E[逻辑 JOIN] --> A
E --> C
ShardingSphere 只生成同后缀表之间的 JOIN。未配置绑定关系时,中间件无法证明两张表的分片拓扑一致,可能尝试更多组合。
绑定表的必要条件不是“字段名相同”,而是:
- 分片键语义一致;
- 分片算法一致;
- 真实节点拓扑一致;
- 业务数据确实满足同键同片;
- JOIN 条件包含绑定关系所需字段。
4.16 广播表不是“大表复制术”
广播表适合低频写、小数据量、各库都需要本地 JOIN 的表。常见候选:地区字典、业务枚举、少量配置。
不适合广播:
- 高频更新库存;
- 百万级商品主数据;
- 大量历史地址;
- 需要严格单点自增的表;
- 变更依赖复杂触发器的表。
每次广播表写操作会扩散到多个数据源,规模越大,写放大和失败处理越复杂。
4.17 单表与默认数据源的工程边界
未分片表不代表可以随意散落。建议按以下规则管理:
1 | |
如果单表未明确配置,某些 DDL 或元数据查询可能采用单播路由,导致“开发库能跑、生产库偶发找错表”的问题。
5. Spring Boot 3.5 整合 ShardingSphere-JDBC
这一节给出完整示例:两个库,每个库两张订单表和两张明细表。
1 | |
分片规则:
1 | |
5.1 pom.xml
1 | |
如果你用 MyBatis,可以额外加:
1 | |
5.2 application.yml
1 | |
这里没有直接写 MySQL 连接信息,因为真实数据源都放在 shardingsphere.yaml 中。
5.3 shardingsphere.yaml
放在:
1 | |
内容如下:
1 | |
注意:ShardingSphere 5.5.3 官方数据源配置示例中使用 standardJdbcUrl。如果你看旧文章,可能看到的是 jdbcUrl。新项目优先按当前官方
5.5.3 文档写。
5.4 建库建表 SQL
1 | |
在两个库里分别执行:
1 | |
5.5 Spring Boot 启动类
1 | |
5.6 Repository 示例
这里用 JdbcTemplate,便于观察 SQL 路由,不引入额外 ORM 复杂度。
1 | |
1 | |
5.7 Service 示例
1 | |
5.8 Controller 示例
1 | |
5.9 测试请求
1 | |
如果开启了:
1 | |
日志里会看到逻辑 SQL 和真实 SQL。
5.10 推荐工程目录
1 | |
5.11 使用 Docker Compose 准备两个 MySQL 实例
为了让示例可复现,建议使用两个独立 MySQL 容器,而不是在同一个实例里只建两个 Schema。独立实例更容易观察连接数、故障隔离与跨库事务行为。
1 | |
启动:
1 | |
对应的 shardingsphere.yaml 需要使用:
1 | |
和:
1 | |
5.12 一份格式完整的初始化 SQL
将下面 SQL 分别放入 docker/ds0/init.sql 和 docker/ds1/init.sql:
1 | |
所有真实分片表必须保持:
- 列名和列顺序一致;
- 数据类型一致;
- 主键和唯一约束一致;
- 索引定义一致;
- 字符集、排序规则一致;
- 默认值和精度一致。
否则会出现某些分片正常、某些分片报错,或者归并时类型不一致。
5.13 使用 KeyHolder 获取 ShardingSphere 生成的主键
原示例插入后通过 order_no 回查主键,能工作,但多一次数据库往返,并依赖 order_no 唯一。更推荐使用 JDBC generated keys:
1 | |
需要导入:
1 | |
5.14 使用 MyBatis 时的 Mapper 示例
1 | |
实体仍使用逻辑表语义,不要在 Mapper 中拼接 t_order_0、t_order_1。
5.15 路由验证不能只看查询结果
至少验证三个层面:
- 逻辑正确性:接口返回结果正确;
- 物理落点正确性:数据进入预期库表;
- 路由数量正确性:没有多余全路由。
人工验证:
1 | |
自动化测试可以抓取 sql-show 日志,或者在 Proxy 中使用 PREVIEW SQL 验证路由。
5.16 启动阶段的健康检查
应用成功启动不代表所有真实数据源都可用。建议增加启动自检:
1 | |
更严格的生产自检应覆盖每个真实数据源。可以在部署流水线中直接连接真实库执行 SELECT 1、检查表结构摘要和索引摘要,不建议让在线应用启动时做高成本全量扫描。
6. 数据分片深度讲解
6.1 路由过程
一次查询大致经历:
flowchart LR
A[逻辑 SQL] --> B[SQL 解析]
B --> C[SQL 路由]
C --> D[SQL 改写]
D --> E[并发执行]
E --> F[结果归并]
F --> G[返回应用]
例如:
1 | |
根据规则:
1 | |
会路由到:
1 | |
如果 SQL 缺少分片键:
1 | |
ShardingSphere 无法判断数据在哪个库哪张表,就可能路由到所有真实表。这就是全路由。全路由不是不能用,但高频接口里出现全路由,数据库基本就开始冒烟了。
6.2 分片键选择原则
分片键建议满足:
- 高频查询条件里一定带它。
- 数据分布足够均匀。
- 业务上不容易变。
- 能让关联表同路由。
- 能减少跨库事务。
订单系统常见选择:
1 | |
如果大多数查询都是按用户查订单,user_id 是不错的分库键。如果订单详情按 order_id 查很多,则要考虑 order_id
是否也参与分片,或者做订单号到分片位置的映射。
6.3 分库键和分表键可以不同吗
可以。
例如:
1 | |
优点:
- 用户维度数据落到固定库。
- 订单维度在库内分散到不同表。
缺点:
- 只带
order_id不带user_id的查询,可能无法精准定位库。
如果业务经常只用 order_id 查订单,建议:
- 让
order_id本身携带分片信息。 - 建立
order_id -> user_id或order_id -> ds的路由索引。 - 查询接口强制要求传入
user_id。 - 使用 Hint 强制路由。
6.4 INLINE 算法
最常见配置:
1 | |
优点:
- 简单。
- 可读性好。
- 适合取模场景。
缺点:
- 默认不适合范围查询。
- 复杂逻辑写起来不优雅。
如果设置:
1 | |
范围查询会被允许,但通常会全路由,不要误以为它能自动精准范围裁剪。
6.5 按时间分片
按月表:
1 | |
示例思路:
1 | |
时间分片适合日志、流水、账单,但要注意:
- 跨月查询会命中多张表。
- 归档策略要提前设计。
- 新月份表要提前创建。
- 历史分片和实时分片的访问频率不同。
6.6 复合分片
当分片需要多个字段:
1 | |
可以使用 complex 或 COMPLEX_INLINE:
1 | |
复合分片不要为了“看起来强大”滥用。多数情况下,一个好的主分片键比多个字段混算更容易维护。
6.7 CLASS_BASED 自定义分片
复杂规则可以用 Java 类。
配置:
1 | |
示例代码:
1 | |
范围查询这里直接返回全部目标,表示全路由。生产里如果要精准范围路由,需要自己根据上下界计算目标表集合。
6.8 Hint 强制路由
当分片键不在 SQL 里,而是在业务上下文中,可以使用 Hint。
1 | |
Hint 使用 ThreadLocal,务必使用 try-with-resources 自动关闭。不关闭会污染当前线程后续请求,这种问题很阴,排查起来让人想喝三杯冰水冷静一下。
6.9 SQL 解析与绑定:不只是把字符串切开
SQL 解析需要识别数据库方言、表、列、条件、占位符、子查询、聚合、排序、分页等信息。随后绑定阶段结合元数据确定:
user_id属于哪张表;- 别名
o指向哪个逻辑表; *具体包含哪些列;- JOIN 条件中的列是否歧义;
- ORDER BY 引用的是列名、别名还是序号;
- 参数索引与分片条件如何对应。
这也是为什么分片环境中元数据一致性非常重要。一个真实表少一列,可能不是立即在启动时报错,而是在特定 SQL 路由到该表时才暴露。
6.10 路由引擎的几种典型路径
ShardingSphere 路由可以从工程角度理解为:
| 路由类型 | 典型触发条件 | 代价 |
|---|---|---|
| Direct Route | Hint 明确指定,场景满足直接路由条件 | 最低,但依赖业务正确提供 Hint |
| Standard Route | 单表或绑定表,带可计算分片条件 | 推荐路径 |
| Cartesian Route | 非绑定表 JOIN、分片拓扑不一致 | 路由组合可能快速膨胀 |
| Full DB/Table Route | 缺少分片键的 DQL/DML | 扫描所有相关真实表 |
| Unicast Route | DESCRIBE 等只需任一真实表 |
单节点执行 |
| Block Route | 不应下发到真实库的逻辑操作 | 被阻断或在逻辑层处理 |
路由数量近似可以表达为:
1 | |
当 JOIN 涉及多张非绑定分片表时,join_combinations 可能成为最危险的一项。
6.11 SQL 改写具体改了什么
改写不只是把 t_order 替换成 t_order_0。常见改写包括:
- 标识符改写:逻辑表、Schema、索引名替换为真实名称;
- 主键补全:INSERT 未传分布式主键时生成并补入;
- 派生列补全:ORDER BY/GROUP BY 所需列未在 SELECT 中时补列;
- AVG 拆分:跨分片
AVG改写为SUM + COUNT后归并; - 分页修正:深分页扩大每个分片的拉取范围;
- 批量语句拆分:一条批量 INSERT 按路由目标拆成多条真实 SQL;
- 单节点优化:只命中一个节点时尽量减少不必要归并。
AVG 不能简单平均每个分片的平均值。例如:
1 | |
所以 SQL 会在各分片计算 SUM 和 COUNT,最后统一计算。
6.12 执行引擎:连接和内存之间的取舍
假设一条 SQL 路由到同一数据库实例中的 200 张表:
- 每张表一个连接,并行快,但可能瞬间占用 200 个连接;
- 一个连接串行跑 200 条 SQL,连接省,但延迟高,而且结果可能需要先加载到内存。
ShardingSphere 执行引擎要在连接资源、并发度和归并内存之间平衡。max-connections-size-per-query 是重要的保护参数,不应盲目调大。
flowchart TD
A[路由得到 N 个执行单元] --> B{每查询允许的最大连接数}
B -->|连接充足| C[更多并行执行]
B -->|严格受限| D[同数据源内串行复用]
C --> E[优先流式归并]
D --> F[可能增加内存归并需求]
E --> G[低延迟但连接消耗高]
F --> H[连接稳定但延迟/内存上升]
6.13 归并引擎详解
归并能力可分为:
- 遍历归并;
- ORDER BY 归并;
- GROUP BY 归并;
- 聚合归并;
- 分页归并。
结构上又可分为:
- 流式归并:ResultSet 逐行读取,通常更省内存;
- 内存归并:先加载结果再统一计算;
- 装饰器归并:在基础归并上叠加分页、聚合等能力。
跨分片 ORDER BY 可以把每个分片看成一个已排序列表,ShardingSphere 用类似优先队列的方式取出全局最小/最大值:
flowchart LR
A[分片0有序结果] --> Q[优先队列]
B[分片1有序结果] --> Q
C[分片2有序结果] --> Q
Q --> R[每次弹出全局下一行]
R --> M[移动对应 ResultSet 游标]
M --> Q
6.14 深分页为什么昂贵
逻辑 SQL:
1 | |
为了保证全局正确性,每个分片通常都要提供足够多的候选记录,近似改写为:
1 | |
如果有 16 个分片,潜在读取量接近:
1 | |
更推荐游标分页:
1 | |
游标分页必须使用稳定、唯一的排序组合,避免同一时间戳导致重复或漏数据。
6.15 全路由审计
当前示例配置了:
1 | |
审计器的价值是把“可能很慢”提前变成“明确拒绝”。但使用前需要划分接口:
- 在线核心接口:建议强制带分片键;
- 后台低频管理查询:可以走独立查询服务;
- 离线任务:可通过批次扫描各分片;
- 数据修复:使用受控 Hint 或直连真实库工具;
- 报表:进入 ClickHouse、Doris、Elasticsearch 或数仓。
不要为了让后台一个搜索框方便,就把整个在线库的分片审计关掉。
6.16 分片算法的边界测试
自定义算法至少测试:
1 | |
取模算法若可能接收负数,应使用 Math.floorMod(value, shardCount),而不是直接 value % shardCount。
6.17 批量 INSERT 的路由与拆分
1 | |
三行可能路由到不同库表,ShardingSphere 需要按目标拆分。批量越大,单次解析、改写和参数复制越重。
生产建议:
- 批次控制在可压测的范围内,例如 100~1000 条,而不是无限堆积;
- 同一批尽量按分片键预分组;
- 关注单包大小、数据库
max_allowed_packet; - 失败时明确整批重试还是分组重试;
- 幂等键必须能跨重试生效。
7. 读写分离
7.1 YAML 示例
1 | |
应用连接的是逻辑数据源 readwrite_ds,不是直接连 write_ds 或 read_ds_0。
7.2 负载均衡算法
内置算法:
RANDOM:随机。ROUND_ROBIN:轮询。WEIGHT:权重。
权重示例:
1 | |
7.3 事务内读策略
1 | |
常见取值:
PRIMARY:事务内读请求路由到主库,默认推荐。FIXED:事务内固定路由到某个数据源。DYNAMIC:事务内动态路由。
普通 MySQL 主从复制存在延迟,所以强烈建议事务内读走主库。
7.4 强制主库路由
某些查询虽然是 SELECT,但业务上必须读主库:
1 | |
例如:
- 刚写完马上查。
- 支付状态确认。
- 库存扣减确认。
- 后台人工审核刚提交的数据。
7.5 读写分离解决的是吞吐,不是数据一致性
主从复制通常是异步或半同步。写入主库后,数据需要经过 binlog、网络传输、relay log 和从库回放,延迟可能从毫秒波动到秒级甚至更高。
sequenceDiagram
participant App as 应用
participant Master as 主库
participant Binlog as Binlog复制
participant Slave as 从库
App->>Master: INSERT order
Master-->>App: COMMIT 成功
Master->>Binlog: 生成并发送日志
App->>Slave: 立即 SELECT
Slave-->>App: 可能查不到
Binlog->>Slave: 回放完成
App->>Slave: 再次 SELECT
Slave-->>App: 查到数据
7.6 一致性等级按业务分类
| 场景 | 推荐读路径 | 原因 |
|---|---|---|
| 创建订单后返回详情 | 主库 | 必须读己之写 |
| 支付结果确认 | 主库 | 状态错误代价高 |
| 库存扣减后确认 | 主库 | 防止超卖判断错误 |
| 用户浏览历史订单 | 从库可接受 | 通常允许轻微延迟 |
| 运营报表 | 从库/OLAP | 吞吐优先 |
| 配置发布后校验 | 主库或版本确认 | 需要确认最新版本 |
不要全局强制所有 SELECT 走主库,否则读写分离形同虚设;也不要全局所有 SELECT 走从库,否则一致性事故只是时间问题。
7.7 会话级“读己之写”设计
除了事务内读主库,可以使用短期主库粘滞:
1 | |
伪代码:
1 | |
窗口值不能拍脑袋,应根据 P99 主从延迟确定,并设置最大保护值。
7.8 从库故障与负载均衡
读库并不是越多越好。每增加一个从库,都增加:
- 复制延迟差异;
- 数据不一致窗口;
- 连接池数量;
- 监控和故障摘除复杂度;
- 权重配置错误风险。
需要监控每个副本:
1 | |
ShardingSphere 负责选择配置中的读数据源,但数据库复制拓扑、故障转移和复制修复仍需数据库层或云数据库承担。
7.9 @Transactional(readOnly = true) 不等于一定走从库
Spring 的 readOnly 主要是事务提示;ShardingSphere 的读写路由还会结合 SQL 类型、事务状态和 transactionalReadQueryStrategy。在配置 PRIMARY 时,即使事务只执行 SELECT,也可能走主库。
因此不要用下面这种直觉做架构保证:
1 | |
真正路由结果必须通过日志或 PREVIEW 验证。
8. 分片 + 读写分离混合规则
典型架构:
1 | |
逻辑上先分库,再在每个分库组里做读写分离。
示例:
1 | |
这里 actualDataNodes 里的 ds_0、ds_1 是读写分离逻辑数据源名称,不是物理库名称。
8.1 混合规则的完整拓扑
flowchart TD
App[Spring Boot 应用] --> SS[ShardingSphere-JDBC]
SS --> Shard{按 user_id 分库}
Shard --> G0[逻辑组 ds_0]
Shard --> G1[逻辑组 ds_1]
G0 --> RW0{读写路由}
RW0 --> M0[(master_0)]
RW0 --> S00[(slave_0_0)]
RW0 --> S01[(slave_0_1)]
G1 --> RW1{读写路由}
RW1 --> M1[(master_1)]
RW1 --> S10[(slave_1_0)]
RW1 --> S11[(slave_1_1)]
M0 --> T00[t_order_0]
M0 --> T01[t_order_1]
M1 --> T10[t_order_0]
M1 --> T11[t_order_1]
逻辑路由顺序可以理解为:先确定分库逻辑组,再在组内选择写库/读库,最后执行表路由。配置中 actualDataNodes 使用的是读写分离逻辑数据源名称。
8.2 配置组合时最容易犯的错误
actualDataNodes写成物理主库名,绕过读写分离逻辑组;- 两个读写组复用了同名底层数据源;
- 主从库表结构不一致;
- 从库没有对应分片表;
- 事务读策略设置为从库,但业务要求读己之写;
- 连接池按物理数据源数倍增后超过数据库上限;
- 故障切换后,ShardingSphere 配置仍指向旧主库。
8.3 连接池总量计算
假设:
1 | |
理论连接上限:
1 | |
还没有计算迁移任务、管理工具、监控、其他服务和数据库保留连接。组合规则上线前必须做全局连接预算。
9. 分布式主键设计
9.1 SNOWFLAKE
1 | |
字段配置:
1 | |
注意点:
- 如果用生成出来的 ID 再取模分片,建议关注
max-vibration-offset。 - 集群模式下
worker-id可以由系统自动生成。 - 单机模式下多实例部署要确保
worker-id不重复。 - 时钟回拨会影响雪花 ID,服务器时间同步必须做好。
9.2 UUID
1 | |
优点:
- 不依赖机器号。
- 多节点天然不重复。
缺点:
- 字符串主键较长。
- 索引局部性较差。
- 排查问题不如 Long 顺手。
9.3 业务建议
对于订单、结算、交易类系统,我更推荐:
1 | |
如果外部暴露 ID,可以另加业务单号:
1 | |
不要把数据库主键、业务单号、幂等号、支付流水号全部混成一个字段。字段少了不代表架构干净,有时候只是以后的人要替你擦桌子。
9.4 Snowflake ID 的结构与分片关系
Snowflake ID 通常由时间、机器标识和序列组成。不同实现位数可能有差异,但核心目标相同:在分布式节点上生成大致有序、全局唯一的长整型 ID。
flowchart LR
A[1 bit<br/>符号位] --> B[41 bits<br/>时间戳差值]
B --> C[10 bits<br/>Worker 标识]
C --> D[12 bits<br/>序列号]
需要注意:如果直接用 Snowflake ID 末位取模分表,短时间内低位分布可能受序列生成方式影响。max-vibration-offset 用于改善分片值的离散程度,但不能替代真实压测。
9.5 Worker ID 管理
多实例部署时,worker ID 冲突会导致主键重复。可选方案:
- Cluster 模式由治理能力分配;
- Kubernetes StatefulSet 序号映射;
- 配置中心分配并加租约;
- 数据库表抢占;
- 直接使用独立 ID 服务。
不推荐:
1 | |
9.6 时钟回拨
必须部署 NTP/chrony 并监控时钟偏移。发生回拨时,根据实现可能等待、拒绝生成或在容忍窗口内处理。
建议监控:
1 | |
9.7 ID 是否应该承载路由信息
三种常见模式:
| 模式 | 查询便利性 | 扩容灵活性 | 复杂度 |
|---|---|---|---|
user_id 分库,order_id 分表 |
用户查询好 | 中 | 中 |
order_id 同时分库分表 |
订单详情好 | 中 | 低 |
| ID 中编码逻辑槽位 | 详情可直达 | 高 | 高 |
在 ID 中编码“物理库编号”会让扩容困难;更合理的是编码稳定的逻辑槽位,再通过槽位映射到物理节点。
10. 数据加密
10.1 加密解决什么问题
数据加密解决的是“数据库中不直接保存明文敏感数据”。
适合字段:
- 手机号。
- 身份证号。
- 银行卡号。
- 邮箱。
- 地址。
- 真实姓名。
10.2 表结构示例
1 | |
逻辑字段是:
1 | |
真实字段是:
1 | |
10.3 YAML 配置
1 | |
10.4 业务 SQL
业务仍然写逻辑字段:
1 | |
查询也写逻辑字段:
1 | |
ShardingSphere 会改写到真实密文字段。
10.5 加密实践建议
- 密钥不要明文写在 Git 仓库。
- 密钥建议接入 KMS、环境变量、配置中心加密能力。
- 加密字段长度要预留足够。
- 查询字段需要辅助查询列,否则等值查询会很难做。
- 模糊查询加密成本高,能不用就不用。
- 旧数据加密迁移要设计双写、回填、校验、切换步骤。
10.6 加密查询的真实代价
加密后的字段通常失去数据库原生索引能力。等值查询可通过确定性加密或辅助查询列实现,但要理解安全权衡:
- 确定性密文会暴露“相同明文产生相同密文”的频率特征;
- Hash 辅助列适合等值查询,不能解密;
- 范围查询很难保持原语义;
- LIKE 查询通常需要专用模糊索引或应用层重构;
- 排序、聚合在密文上通常没有业务意义。
10.7 密钥管理架构
flowchart LR
App[Spring Boot] --> SS[ShardingSphere Encrypt Rule]
SS --> KMS[KMS / Vault]
KMS --> SS
SS --> DB[(密文列 + 辅助查询列)]
CI[CI/CD] -.不保存明文密钥.-> App
建议:
- Git 仓库只保存密钥引用,不保存真实密钥;
- 不同环境使用不同密钥;
- 密钥读取权限与数据库权限分离;
- 建立密钥轮换版本;
- 审计谁在何时读取过密钥;
- 备份恢复时同步验证密钥可用性。
10.8 老数据加密迁移步骤
flowchart TD
A[新增 cipher/hash 列] --> B[上线兼容读写逻辑]
B --> C[新数据写入密文]
C --> D[批量回填历史数据]
D --> E[按主键/摘要校验]
E --> F[灰度切换逻辑列]
F --> G[观察错误和查询命中率]
G --> H[清理明文列或收紧权限]
回填任务必须:
- 按主键分批;
- 可暂停、可续跑;
- 幂等;
- 限速;
- 记录失败主键;
- 校验密文可解密、Hash 可命中;
- 在切换前完成备份。
10.9 密钥轮换
密钥轮换不能直接把配置中的 key 改掉,否则旧密文无法解密。常见做法是:
1 | |
先支持双版本解密,新写入使用 v2,再后台重加密旧数据,校验完成后停止 v1 写入,最后下线旧密钥。
11. 数据脱敏
11.1 脱敏和加密的区别
1 | |
11.2 YAML 示例
1 | |
11.3 使用建议
- 后台管理系统可以用脱敏降低误泄露风险。
- 不同角色需要不同脱敏策略时,建议在应用层结合权限控制。
- 脱敏不是权限系统,不要用脱敏替代鉴权。
- 审计日志里也要注意敏感字段,不然数据库脱敏了,日志又把数据泄露出去。
11.4 脱敏应放在哪一层
| 层级 | 优点 | 缺点 |
|---|---|---|
| ShardingSphere MASK | 对 SQL 透明、规则统一 | 难表达复杂角色权限 |
| 应用 DTO | 可按角色、场景精细控制 | 容易漏接口 |
| API Gateway | 可统一出口 | 不理解所有业务语义 |
| 前端 | 展示灵活 | 不能作为安全边界 |
推荐组合:数据库存储加密 + ShardingSphere/应用层脱敏 + 权限控制 + 日志脱敏。前端脱敏只能改善展示,不能阻止接口泄漏。
11.5 日志和异常中的敏感数据
重点检查:
sql-show是否打印手机号、身份证;- MyBatis 参数日志;
- 异常堆栈中的 SQL 参数;
- Controller 请求体日志;
- MQ 消息体;
- 链路追踪 Span Attribute;
- 数据修复脚本输出;
- APM 慢 SQL 样本。
生产环境建议关闭完整 SQL 参数打印,改为 SQL 指纹 + 参数类型 + 路由数。
12. 影子库
12.1 适用场景
影子库适合全链路压测:
- 生产流量和压测流量共用应用链路。
- 压测数据不能污染生产库。
- 需要模拟真实环境下的数据库访问。
12.2 YAML 示例
1 | |
12.3 SQL Hint 示例
1 | |
12.4 注意事项
- 影子库表结构要和生产库保持一致。
- 压测数据要有清理策略。
- 压测标记要贯穿 HTTP、MQ、RPC、SQL。
- 不要让影子规则误伤正常生产流量。
12.5 影子标记必须贯穿全链路
只有数据库层识别压测标记是不够的。一个完整压测请求可能经过:
flowchart LR
A[HTTP Header] --> B[Gateway]
B --> C[RPC Metadata]
C --> D[业务服务]
D --> E[MQ Header]
E --> F[消费者]
F --> G[SQL Hint / 影子字段]
G --> H[(影子数据库)]
任意一环丢标,压测流量都可能进入生产数据。
12.6 影子库上线前验证
- 正常流量 100% 进入生产库;
- 带标流量 100% 进入影子库;
- UPDATE/DELETE 不误伤生产数据;
- DDL 变更同步到影子库;
- 影子库容量和索引接近生产;
- 影子数据有独立账号、清理和备份策略;
- 压测结束后标记不会残留在线程池、消息或缓存中。
Hint 基于线程上下文时,必须使用 try-with-resources,虚拟线程、异步任务和 Reactor 场景还要验证上下文传播,不能默认 ThreadLocal 会自动跨边界。
13. 分布式事务
13.1 三种模式
1 | |
ShardingSphere 支持:
| 模式 | 说明 | 建议 |
|---|---|---|
| LOCAL | 本地事务 | Spring Boot 3.5 默认优先使用 |
| XA | 强一致分布式事务 | Spring Boot 3.x 场景谨慎,需验证兼容性 |
| BASE | 柔性事务,常见为 Seata | 适合最终一致性场景 |
13.2 LOCAL 模式
1 | |
LOCAL 模式下,如果一次事务只命中一个真实库,效果接近普通本地事务。
如果一次事务写多个真实库,就不要把它理解成强一致分布式事务。业务上要能接受异常场景下的补偿、对账和恢复。
13.3 XA 模式
1 | |
Spring Boot 3.5 下,XA 要非常谨慎。官方文档对 Spring Boot OSS 3 已给出限制提醒。生产使用前至少要验证:
- 启动兼容性。
- 多库 commit。
- 某个库 commit 失败。
- 网络闪断。
- 应用重启恢复。
- 连接池回收。
- 事务超时。
- 压测下延迟。
13.4 BASE / Seata
1 | |
BASE 更偏最终一致性。需要额外部署 Seata Server,并设计全局事务、分支事务、undo 日志、异常恢复。
13.5 工程建议
真正稳定的分库分表系统,通常不是靠到处上分布式事务,而是靠:
- 分片键让同一业务聚合落同库。
- 明确聚合边界。
- 使用本地事务完成单库写入。
- 跨库动作通过消息、任务、补偿、对账完成。
- 关键状态机可重入、可恢复、可幂等。
13.6 为什么 LOCAL 跨库不是强一致
一次 Spring 事务命中两个物理库:
sequenceDiagram
participant App as 应用事务
participant DB0 as ds_0
participant DB1 as ds_1
App->>DB0: UPDATE A
App->>DB1: UPDATE B
App->>DB0: COMMIT
DB0-->>App: 成功
App->>DB1: COMMIT
DB1--xApp: 网络故障
此时 DB0 已提交,DB1 未提交。普通本地事务无法让已经提交的 DB0 自动回滚。
13.7 事务方案决策表
| 业务要求 | 推荐方案 | 说明 |
|---|---|---|
| 单聚合、同分片写入 | LOCAL | 最简单、性能最好 |
| 跨库但允许秒级一致 | Outbox + MQ + 补偿 | 常见推荐 |
| 跨库且要求强一致 | XA,严格验证 | Boot 3 场景有明确限制 |
| 长事务、外部服务参与 | Saga/TCC/状态机 | 业务复杂度高 |
| 批量离线同步 | 可重入任务 + 对账 | 不要包超大事务 |
13.8 Outbox 最终一致性示例
同一个本地事务写业务表和消息表:
1 | |
flowchart LR
A[本地事务] --> B[(业务表)]
A --> C[(local_event)]
C --> D[发布任务]
D --> E[MQ]
E --> F[下游消费者]
F --> G[(下游分片库)]
D -.失败重试.-> C
G --> H[对账/补偿]
关键要求:
- 事件 ID 全局唯一;
- 发布端至少一次投递;
- 消费端幂等;
- 失败指数退避;
- 死信和人工处理;
- 业务状态与事件状态可对账。
13.9 @Transactional 的常见误区
- 同类方法内部调用导致事务代理失效;
- 异步线程不继承原事务;
- 捕获异常后不重新抛出,事务不会回滚;
rollbackFor未覆盖检查异常;- 一个事务里先查从库再写主库,读到旧值;
- 事务跨多个物理库却误以为 LOCAL 强一致;
- 远程 RPC 不属于本地数据库事务。
13.10 事务故障演练
至少模拟:
1 | |
没有故障演练的分布式事务设计,通常只验证了“世界和平时能工作”。
14. ShardingSphere-Proxy
14.1 为什么需要 Proxy
JDBC 适合 Java 应用内嵌,Proxy 适合平台化。
Proxy 价值:
- 多语言统一接入。
- 规则集中管理。
- 支持 DistSQL。
- 运维视角更清晰。
- 应用无需引入 ShardingSphere 依赖。
14.2 启动方式
常见方式:
- 二进制包。
- Docker。
- Helm。
二进制启动:
1 | |
默认端口通常是:
1 | |
连接:
1 | |
如果后端是 MySQL,二进制包方式通常需要把 MySQL 驱动放到:
1 | |
14.3 Proxy 配置文件
核心文件:
1 | |
global.yaml 管全局配置,例如权限、事务、模式、属性。
database-xxx.yaml 管逻辑库数据源和规则。
14.4 Proxy 与 JDBC 的选择
| 对比项 | ShardingSphere-JDBC | ShardingSphere-Proxy |
|---|---|---|
| 部署方式 | 应用内 Jar | 独立服务 |
| 性能 | 更少网络跳转 | 多一层代理 |
| 多语言 | Java 友好 | 多语言友好 |
| 规则管理 | 跟随应用 | 集中治理 |
| 运维复杂度 | 低 | 中等 |
| DistSQL | 不作为主入口 | 核心能力 |
| 适合场景 | 单 Java 应用 / 微服务 | 平台化数据库网关 |
14.5 Proxy 的完整部署拓扑
flowchart TD
Client1[Java 服务] --> LB[四层负载均衡]
Client2[Go 服务] --> LB
Client3[BI / MySQL Client] --> LB
LB --> P1[Proxy-1]
LB --> P2[Proxy-2]
LB --> P3[Proxy-3]
P1 <--> Registry[(ZooKeeper / Etcd)]
P2 <--> Registry
P3 <--> Registry
P1 --> DB0[(ds_0)]
P1 --> DB1[(ds_1)]
P2 --> DB0
P2 --> DB1
P3 --> DB0
P3 --> DB1
生产至少考虑:
- Proxy 无状态计算节点横向扩容;
- 注册中心高可用;
- 负载均衡健康检查;
- 长连接摘除和优雅下线;
- Proxy 到每个真实库的连接池预算;
- 配置变更审计;
- 管理端口与业务端口隔离;
- TLS、账号权限和网络 ACL。
14.6 Docker 启动 Proxy 的示例思路
镜像标签和目录结构应以 5.5.3 发布包为准。通常流程是:
1 | |
生产不要省略发布包校验:
1 | |
14.7 Proxy 的连接模型
客户端连接数和 Proxy 到后端库的连接数不是一一固定相等,但两侧都需要预算:
1 | |
重点指标:
1 | |
14.8 Proxy 高可用不是只起两个实例
还需要验证:
- 某实例停止后客户端是否自动重连;
- 事务中的连接断开如何呈现;
- PreparedStatement 是否能在重连后正确重建;
- DistSQL 变更是否被所有实例感知;
- 注册中心短暂不可用时,现有流量是否继续;
- 节点恢复后元数据是否一致;
- LB 是否能优雅摘除正在执行长 SQL 的节点。
14.9 JDBC 与 Proxy 混用的治理原则
如果同一逻辑库既被 JDBC 又被 Proxy 访问:
- 规则必须有唯一事实源;
- 禁止一边静态 YAML、一边 DistSQL 随意修改;
- 发布前生成规则指纹并比对;
- 使用相同的逻辑表、算法和数据节点命名;
- 建立统一迁移窗口;
- 任何扩容先验证两种入口路由一致。
15. DistSQL
DistSQL 是 Proxy 的灵魂之一。它让你用 SQL 管理分布式数据库规则。
15.1 注册存储单元
1 | |
15.2 创建分片规则
1 | |
15.3 常用查看命令
1 | |
15.4 预览 SQL 路由
1 | |
这个命令非常适合排查路由问题。路由问题不要靠猜,猜数据库路由跟猜对象心思差不多,都容易误伤自己。
15.5 DistSQL 变更必须像数据库 DDL 一样管理
推荐流程:
flowchart TD
A[提交 DistSQL 变更脚本] --> B[代码评审]
B --> C[测试环境 PREVIEW/回归]
C --> D[备份当前规则]
D --> E[生产变更审批]
E --> F[执行 DistSQL]
F --> G[SHOW RULE 验证]
G --> H[路由冒烟测试]
H --> I{是否正常}
I -->|是| J[记录审计与规则指纹]
I -->|否| K[执行回滚脚本]
15.6 规则导出与漂移检测
每天或每次变更后导出:
1 | |
对输出做规范化后计算 SHA-256:
1 | |
多环境、多 Proxy 节点出现不同指纹时立即告警。
15.7 DistSQL 权限隔离
建议划分账号:
1 | |
业务应用账号不应拥有规则修改权限。否则一次 SQL 注入或误操作可能改变整个逻辑库路由。
15.8 PREVIEW SQL 的使用场景
上线前至少对以下 SQL 建路由快照:
- 主键点查;
- 分片键 IN 查询;
- 范围查询;
- 绑定表 JOIN;
- 非绑定 JOIN;
- 分页;
- 聚合;
- 广播表写入;
- 单表查询;
- Hint 查询。
每次规则变更后对比快照,可以快速发现路由数量变化。
16. 数据迁移
16.1 什么时候需要迁移
典型场景:
- 单库单表迁移到分库分表。
- 分片数量从 2 扩到 4。
- 旧数据源迁移到新数据源。
- 调整分片键。
- 迁移到 Proxy 统一治理。
16.2 迁移思路
flowchart TD
A[准备新库新表] --> B[配置目标分片规则]
B --> C[全量迁移]
C --> D[增量同步]
D --> E[数据一致性校验]
E --> F[灰度切流]
F --> G[观察与回滚窗口]
G --> H[正式切换]
16.3 迁移前检查
- 源表必须有稳定主键。
- 分片键不能为空。
- 目标表结构和索引提前创建。
- 目标库容量、连接数、磁盘 IO 足够。
- 双写或增量同步链路可观测。
- 有回滚方案。
- 有数据校验方案。
16.4 数据校验
至少校验:
- 行数。
- 主键范围。
- 核心金额字段合计。
- 状态分布。
- 更新时间最大值。
- 抽样比对。
订单、财务、结算系统只校验行数是不够的,金额合计一定要校验。
16.5 扩容不是修改取模数
原规则:
1 | |
直接改成:
1 | |
会让大量旧数据的计算落点改变。如果数据未同步迁移,新规则会去新位置查旧数据,结果就是“数据消失”。
16.6 扩容的四个阶段
flowchart LR
A[双节点旧拓扑] --> B[准备四节点新拓扑]
B --> C[全量复制旧数据]
C --> D[增量同步]
D --> E[一致性校验]
E --> F[灰度切读]
F --> G[灰度切写]
G --> H[停止旧链路]
16.7 虚拟槽位降低扩容重映射
可以先把分片键映射到较稳定的逻辑槽位,再把槽位映射到物理库:
1 | |
flowchart LR
U[user_id] --> S[1024 个逻辑槽位]
S --> D0[ds_0: slot 0-511]
S --> D1[ds_1: slot 512-1023]
D0 -.扩容迁移部分槽位.-> D2[ds_2]
D1 -.扩容迁移部分槽位.-> D3[ds_3]
扩容时只迁移部分槽位,不改变所有数据的逻辑槽位。ShardingSphere 可通过自定义 CLASS_BASED 算法实现槽位映射,但映射表本身要高可用、版本化和可审计。
16.8 迁移 Runbook
迁移前:
1 | |
全量阶段:
1 | |
增量阶段:
1 | |
校验阶段:
1 | |
切流阶段:
1 | |
16.9 财务数据校验示例
1 | |
按状态校验:
1 | |
金额校验要使用相同精度和舍入规则,不能一边 DECIMAL、一边转 double。
16.10 回滚不是“把开关切回去”
切写后新库已经产生新数据,回滚到旧库需要反向同步或短暂双写。回滚方案必须提前回答:
- 新拓扑产生的数据如何回旧拓扑;
- 新旧主键是否冲突;
- 消息是否重复消费;
- 规则回退后读写落点是否一致;
- 回滚窗口多久;
- 超过窗口后是否改为向前修复。
16.11 迁移限流
迁移任务与在线流量争抢:
1 | |
限流应根据在线 P99 延迟和数据库资源动态调整,而不是固定线程数跑到底。
17. 可观察性
17.1 SQL 日志
开发环境开启:
1 | |
生产环境谨慎开启。SQL 日志可能包含敏感数据,也可能造成额外 IO 压力。
17.2 Metrics
ShardingSphere-Agent 支持指标采集,常见指标包括:
parsed_sql_totalrouted_sql_totalrouted_result_totaljdbc_statejdbc_statement_execute_totaljdbc_statement_execute_errors_total
这些指标适合接入 Prometheus / Grafana。
17.3 建议监控项
应用层:
- 接口耗时。
- 慢 SQL 数量。
- 连接池活跃连接数。
- 连接池等待数。
- 错误 SQL 数量。
ShardingSphere 层:
- 解析 SQL 总数。
- 路由 SQL 总数。
- 全路由比例。
- 路由结果数量。
- 执行错误数量。
数据库层:
- QPS / TPS。
- 慢查询。
- CPU / IO。
- 连接数。
- 主从延迟。
- 锁等待。
17.4 最值得盯的指标
我最建议重点盯:
1 | |
分库分表系统里,全路由是性能事故的温柔前奏。它一开始只是慢一点,后来就会很有存在感。
17.5 推荐日志字段
不要只打印一条“Actual SQL”。建议结构化记录:
1 | |
示例 JSON:
1 | |
注意不要记录完整敏感参数。
17.6 监控面板分层
flowchart TD
A[业务 SLO] --> B[应用层]
B --> C[ShardingSphere 层]
C --> D[连接池层]
D --> E[数据库层]
E --> F[复制与存储层]
**业务层:**成功率、P95/P99、超时率、核心交易量。
**应用层:**线程、虚拟线程 pin、GC、堆、CPU、请求队列。
**ShardingSphere 层:**解析、路由、改写、执行、归并耗时,路由单元数,全路由比例。
**连接池层:**active、idle、pending、timeout、创建连接耗时。
**数据库层:**QPS/TPS、锁等待、慢 SQL、Buffer Pool、IO、连接数。
**复制层:**主从延迟、复制错误、位点差距。
17.7 ShardingSphere-Agent 示例
Agent 支持 Metrics、Tracing 和 Logging 插件。示意配置:
1 | |
具体插件名称和属性以 5.5.3 发布包中的 agent.yaml 为准。
17.8 告警建议
| 指标 | 告警示例 | 说明 |
|---|---|---|
| 全路由比例 | 5 分钟 > 1% | 在线接口通常应接近 0 |
| 单 SQL 路由单元 | P99 > 16 | 可能出现路由放大 |
| 连接池 pending | 持续 > 0 | 连接不足或 SQL 过慢 |
| 获取连接超时 | 任意增长 | 高优先级 |
| 主从延迟 | 超过业务一致性窗口 | 读旧数据风险 |
| merge P99 | 持续升高 | 深分页/聚合问题 |
| duplicate key | 异常增长 | ID/幂等/迁移冲突 |
| 元数据加载失败 | 任意出现 | 规则或真实表不一致 |
17.9 SQL 指纹与 TopN
将常量参数归一化:
1 | |
按 SQL 指纹统计:
1 | |
同一 SQL 指纹的路由数量突然变化,往往意味着代码条件丢失、规则变更或参数类型异常。
17.10 可观测性本身的成本
- 全量 SQL 日志会增加 IO;
- 全量 Trace 会增加网络和存储;
- 高基数标签会拖垮 Prometheus;
- 参数日志可能泄露数据;
- 慢 SQL采样过低会漏问题。
推荐:Metrics 全量、Trace 采样、错误 Trace 全量、SQL 参数默认不记录。
18. SQL 支持与限制
分库分表不是完整分布式数据库,SQL 能力一定有边界。
18.1 友好的 SQL
1 | |
特点:
- 带分片键。
- 路由明确。
- 查询范围小。
- 排序分页在单分片内完成。
18.2 危险 SQL
1 | |
问题:
- 缺少分片键。
- 可能全库全表扫描。
- 跨分片排序分页成本高。
- count 需要多分片归并。
18.3 关联查询
好的关联:
1 | |
前提:
- 两张表是绑定表。
- 分片策略一致。
- 查询条件带分片键。
危险关联:
1 | |
如果 t_user 没有相同分片策略,可能出现跨库关联或全路由。
18.4 SQL 兼容性要用业务 SQL 集验证
不要只看官方“支持 SELECT/INSERT/UPDATE/DELETE”。真实 SQL 复杂度来自:
1 | |
建立 SQL 兼容性测试集,将生产 Top SQL、核心 Mapper 和报表 SQL全部纳入升级回归。
18.5 聚合查询的风险矩阵
| SQL | 是否可归并 | 性能风险 |
|---|---|---|
COUNT(*) |
通常可以 | 全路由 + 多分片求和 |
SUM(amount) |
通常可以 | 扫描量大 |
AVG(amount) |
改写为 SUM/COUNT | 多字段归并 |
MIN/MAX |
通常可以 | 仍需查询各分片 |
GROUP BY key |
可以但可能内存归并 | 高基数风险 |
COUNT(DISTINCT x) |
需重点验证 | 去重成本高 |
| 多维 GROUP BY + ORDER BY | 需重点压测 | 内存与排序风险 |
18.6 管理端搜索如何处理
管理端常见条件:
1 | |
它们不一定包含主分片键。可选架构:
flowchart LR
A[业务写入] --> B[(分片 OLTP)]
A --> C[CDC / MQ]
C --> D[(Elasticsearch)]
C --> E[(ClickHouse / Doris)]
F[管理端点查] --> B
G[复杂搜索] --> D
H[报表聚合] --> E
不要强迫 ShardingSphere 承担所有查询类型。
18.7 分布式分页的正确接口设计
不推荐:
1 | |
推荐:
1 | |
Cursor 中包含:
1 | |
Cursor 应签名或加密,防止客户端随意篡改。
18.8 UPDATE/DELETE 必须带分片键
危险:
1 | |
如果 order_no 不是分片键,会向多个分片发送 UPDATE。即使最终只命中一行,也产生写放大和锁竞争。
推荐:
1 | |
同时利用状态条件保证幂等状态转换。
18.9 DDL 管理
分片表 DDL 必须同步所有真实表:
flowchart TD
A[Flyway 逻辑迁移定义] --> B[生成真实库表脚本]
B --> C[预检查所有分片]
C --> D[分批执行]
D --> E[Schema 摘要校验]
E --> F[应用发布]
高风险变更:
- 大表加非空默认值列;
- 重建大索引;
- 修改主键;
- 修改分片键;
- 改字符集;
- 同时变更所有分片导致 IO 峰值。
推荐使用 Online DDL 工具或云数据库在线变更能力,并控制并发分片数。
19. 分片建模最佳实践
19.1 按业务聚合设计
分片不要只看表大小,要看业务聚合。
例如订单系统:
1 | |
如果用户维度查询最多,可以考虑 user_id。
如果商家维度结算最多,可以考虑 shop_id。
如果租户隔离最重要,可以考虑 tenant_id。
没有绝对正确的分片键,只有最符合业务主路径的分片键。
19.2 避免跨库事务
设计时尽量让一次事务内修改的数据落在同一个库:
1 | |
如果做不到,就要补充:
- 幂等键。
- 事务消息。
- 补偿任务。
- 对账任务。
- 异常状态机。
19.3 冷热数据分离
大表经常不是平均热,而是近期数据热、历史数据冷。
可以考虑:
- 当前表 + 历史表。
- 按月分表。
- 热数据 MySQL,冷数据归档到 OLAP。
- 搜索查询走 Elasticsearch / ClickHouse / Doris。
ShardingSphere 解决 OLTP 分片问题,不要让它独自扛所有分析型查询。
19.4 分片数量不要乱定
分片数量要考虑:
- 当前数据量。
- 三年增长量。
- 单表可接受大小。
- 数据库实例容量。
- 扩容复杂度。
- 运维成本。
不要一上来就 1024 张表。表多不是架构先进,有时候只是把复杂度提前透支。
19.5 什么时候需要分库分表
不要只用“单表超过 500 万/2000 万”作为标准。真正判断维度:
| 维度 | 问题 |
|---|---|
| 容量 | 三年后数据和索引能否放下 |
| 写入 | 单实例 TPS 是否达到瓶颈 |
| 查询 | 核心 SQL 是否因数据规模退化 |
| 连接 | 单实例能否承载应用连接总量 |
| 运维 | DDL、备份、恢复窗口是否不可接受 |
| 隔离 | 大租户是否影响其他租户 |
| 成本 | 分片复杂度是否低于扩容硬件成本 |
19.6 分片数量估算
可以使用三个约束分别计算,取最大值:
1 | |
例如:
1 | |
可以选择 96 或 128 个逻辑槽位,但不意味着立即创建同等数量物理库。表数、槽位数和库数可以分层设计。
19.7 容量规划表
上线前至少记录:
1 | |
19.8 租户分片策略
SaaS 常见三类:
tenant_id % N:简单,但大租户可能形成热点;- 大租户独占库,小租户共享库:隔离好,但路由表复杂;
- 逻辑槽位 + 租户映射:扩容灵活,治理成本高。
大租户不能只按租户 ID 取模。一个超级租户可能独占整个分片的绝大多数流量。
19.9 热点与数据倾斜
平均分布不代表请求均匀。需要分别评估:
1 | |
一个分片只有 10% 数据,也可能承载 80% 热点请求。
19.10 分片键变更为什么困难
分片键决定物理位置。修改它相当于全量重新分布数据,还会影响:
- 查询接口参数;
- 唯一约束;
- 绑定表;
- 事务边界;
- 下游事件;
- 数据归档;
- 缓存 Key;
- 路由索引。
因此分片前应收集真实 SQL 和访问模式,而不是只开架构会拍脑袋。
20. 生产配置建议
20.1 props 建议
开发环境:
1 | |
生产环境:
1 | |
kernel-executor-size 不是越大越好,要结合 CPU、连接池、真实数据库能力调。
20.2 连接池建议
每个真实数据源都有自己的连接池。假设:
1 | |
最大连接数理论上是:
1 | |
这还没算其他应用。很多数据库连接数爆掉,不是业务突然变猛,而是连接池配置像开自助餐,大家都拿满了。
20.3 推荐连接池计算方式
先从小值开始:
1 | |
观察:
- Hikari active connection。
- pending threads。
- DB CPU。
- SQL 平均耗时。
- 慢查询。
再逐步调整。
20.4 HikariCP 与 ShardingSphere 双层理解
Spring Boot 创建的是 ShardingSphere 逻辑 DataSource,ShardingSphere 内部又为每个真实数据源创建连接池。配置中的:
1 | |
是 ds_0 这个真实数据源池的上限,不是整个应用的总上限。
20.5 连接池预算公式
1 | |
数据库允许连接还需扣除:
1 | |
建议数据库目标使用率不要长期超过连接上限的 70%~80%。
20.6 连接池参数建议
| 参数 | 建议 |
|---|---|
maximumPoolSize |
从 10~20 起步,结合压测调 |
minimumIdle |
不要等于过大的 maximum,防止启动连接风暴 |
connectionTimeout |
通常 1~5 秒比 30 秒更利于快速失败,需结合业务 |
idleTimeout |
结合数据库 wait_timeout |
maxLifetime |
略小于数据库/网络设备连接生命周期 |
validationTimeout |
保持较短 |
keepaliveTime |
仅在网络空闲连接易被切断时启用 |
20.7 JDK 21 虚拟线程不会增加数据库连接
虚拟线程可以降低阻塞线程成本,但数据库连接仍是稀缺资源:
flowchart LR
A[10000 个虚拟线程请求] --> B[Hikari 连接池 20]
B --> C[(MySQL)]
A -.其余请求等待连接.-> B
因此,不能因为启用虚拟线程就把连接池调到几百。正确做法是控制并发、优化 SQL、缩短持有连接时间。
20.8 kernel-executor-size 调优
该参数影响 ShardingSphere 内核执行并发。过小会限制多路由执行速度,过大会:
- 增加线程竞争;
- 放大后端连接请求;
- 增加数据库瞬时 QPS;
- 让慢 SQL 并行压垮所有分片。
调优步骤:
1 | |
20.9 生产 YAML 安全化
不要在 Git 中写:
1 | |
可选方式:
- 容器环境变量渲染配置;
- Kubernetes Secret + CSI;
- Vault Agent 模板;
- 配置中心密文;
- 云数据库 IAM 临时凭证(若支持)。
配置文件权限至少限制为应用用户可读。
20.10 启动连接风暴
Kubernetes 一次滚动启动 50 个实例,每实例 8 个数据源、minimumIdle=10:
1 | |
解决:
- 控制
maxSurge; - 降低
minimumIdle; - 启动随机抖动;
- 数据库代理/连接复用;
- 分批发布;
- 监控数据库登录和握手耗时。
20.11 推荐的生产属性基线
下面只是起点,不是通用答案:
1 | |
上线前应通过压测决定 kernel-executor-size 和 max-connections-size-per-query。
21. 常见问题与排查
21.1 启动报错找不到 ShardingSphereDriver
检查依赖:
1 | |
检查配置:
1 | |
21.2 YAML 文件没加载
检查:
- 文件是否在
src/main/resources。 - 文件名是否和
classpath:shardingsphere.yaml一致。 - YAML 缩进是否正确。
rules下的!SHARDING、!READWRITE_SPLITTING是否大写。
21.3 表不存在
检查:
- 真实库是否存在。
- 真实表是否都创建。
actualDataNodes是否写错。- 分片算法是否路由到了不存在的表。
21.4 查询很慢
优先看:
- 是否缺少分片键。
- 是否全路由。
- 是否跨分片排序。
- 是否跨分片分页。
- 是否广播表太大。
- 是否连接池不够。
- 是否真实库索引缺失。
开启 sql-show 看真实 SQL 是第一步。
21.5 插入后 ID 为空
检查:
1 | |
以及:
1 | |
同时确认 INSERT SQL 中没有主动传入空 ID 导致行为异常。
21.6 主从延迟导致读不到刚写数据
解决:
- 事务内使用
transactionalReadQueryStrategy: PRIMARY。 - 关键读请求使用
HintManager.setWriteRouteOnly()。 - 业务允许延迟时再读从库。
- 监控主从延迟。
21.7 全路由太多
解决:
- 改接口入参,强制带分片键。
- 建路由索引。
- 用 Hint 指定路由。
- 对管理端查询单独建搜索索引或报表库。
- 调整分片键。
21.8 系统化排障流程
flowchart TD
A[发现慢/错/数据缺失] --> B[确认逻辑 SQL 与参数]
B --> C[检查规则版本和指纹]
C --> D[查看路由目标数量]
D --> E[查看真实 SQL]
E --> F[直连真实库 EXPLAIN]
F --> G[检查连接池与数据库资源]
G --> H[检查归并、主从延迟、事务]
H --> I[复现并补充自动化测试]
21.9 案例:同一查询偶尔查不到数据
排查顺序:
- 是否刚写完读从库;
- 是否查询条件缺分库键;
order_id和user_id是否属于同一条数据;- 是否迁移期间新旧规则不一致;
- 是否某个分片表结构或数据缺失;
- Hint 是否在线程中残留;
- 数据是否写到了错误 worker/算法计算的节点。
21.10 案例:CPU 不高但接口大量超时
可能是:
- Hikari pending;
- 数据库连接上限;
- 网络连接建立慢;
- 锁等待;
- 全路由串行执行;
- 主从切换;
- 结果集消费慢;
- 应用下游阻塞但事务一直持有连接。
先看连接池等待和事务持有时间,不要只盯 CPU。
21.11 案例:增加索引后仍然很慢
单个真实 SQL 使用索引,不代表整体快:
1 | |
需要减少路由数,而不是只优化每个分片。
21.12 案例:线上走全路由,测试环境精确路由
检查:
- 两环境 YAML 是否一致;
- 分片键参数类型是否一致;
- 线上 SQL 是否被动态条件删除;
- MyBatis 是否传 null;
- 数据库字段类型和 Java 类型;
- ShardingSphere 版本;
- 自定义算法配置;
- 规则中心是否存在旧配置;
- Hint 是否仅在测试代码中设置。
21.13 案例:应用启动很慢
可能原因:
- 数据源数量多;
- 元数据检查;
- 单表
*.*扫描; - 网络 DNS 慢;
- 数据库握手慢;
- 连接池预热过大;
- 真实表数量巨大。
可以分别记录:
1 | |
21.14 案例:内存突然上涨
重点看:
- 跨分片 GROUP BY;
- 深分页;
- 大结果集未设置合理 fetch size;
- 连接受限导致流式归并退化;
- SQL 日志大量字符串;
- 一次批量写过大;
- 客户端读取结果过慢。
21.15 路由问题最小复现模板
提交问题或内部排查时,至少准备:
1 | |
没有参数类型的 SQL 日志经常不够,因为字符串 "10" 与 Long 10L 可能走不同类型处理路径。
22. 和 MyBatis / JPA 的整合建议
22.1 MyBatis
MyBatis 不需要特别感知 ShardingSphere,只要使用 Spring Boot 的 DataSource 即可。
1 | |
Mapper 里写逻辑 SQL:
1 | |
不要在 Mapper 里写真实表名:
1 | |
这会绕开逻辑表模型,后期维护会很难受。
22.2 JPA
JPA 也可以接 ShardingSphere DataSource,但要注意:
- Entity 表名写逻辑表名。
- 不要依赖数据库自增主键。
- 关闭复杂级联。
- 谨慎使用跨表关联。
- 分库分表场景下复杂查询更适合 MyBatis / SQL。
示例:
1 | |
JPA 的自动 DDL 不建议用于分片表创建,生产环境应该由 Flyway、Liquibase 或 DBA 脚本管理真实表。
22.3 MyBatis 动态 SQL 的分片键丢失
1 | |
当 userId=null 时,SQL 仍合法,却可能全路由。建议:
- 在线接口对分片键使用 Bean Validation;
- Mapper 方法区分“按分片查询”和“管理查询”;
- 分片键不能为空时直接抛错;
- 使用 DML_SHARDING_CONDITIONS 审计器;
- 单元测试覆盖动态条件缺失。
22.4 MyBatis-Plus 注意事项
selectById(orderId)只有主键,若分库键是user_id,可能跨库;- Wrapper 条件需要显式加入分片键;
- 分页插件与 ShardingSphere 分页会叠加,必须验证生成 SQL;
- 批量方法可能被拆分到多个分片;
- 逻辑删除字段不会自动成为分片键;
- 自动填充主键与 ShardingSphere key generator 不要重复生成。
推荐方法签名:
1 | |
而不是在分库规则需要 userId 时只提供 selectById。
22.5 JPA 的 N+1 在分片环境更危险
普通单库的 N+1 已经很慢;分片环境中每次子查询还可能全路由:
1 | |
建议:
- 禁用无意识懒加载;
- 使用显式 DTO 查询;
- 绑定表 JOIN;
- 批量按分片键分组查询;
- 在测试中统计 SQL 数和路由单元数。
22.6 Flyway 与分片表
Flyway 默认针对一个 DataSource 维护一张历史表。分片环境有三种做法:
- 独立管理数据源维护迁移历史,脚本显式更新所有真实库;
- 每个真实库独立 Flyway 实例;
- 在 CI/CD 中生成并执行物理 DDL,不由业务应用启动时迁移。
生产更推荐第三种或受控的第二种。不要让几十个应用副本同时执行同一批分片 DDL。
22.7 Repository 层接口应体现路由约束
不推荐:
1 | |
当分库键是 userId 时,推荐:
1 | |
如果业务只有 orderId,需要设计:
1 | |
而不是默认广播查询。
23. 工程落地清单
23.1 接入前必须回答的问题
- 数据量为什么需要分库分表?
- 当前瓶颈是存储、查询、写入、连接数,还是索引设计?
- 主要查询路径是什么?
- 分片键是什么?
- 是否存在大量不带分片键的查询?
- 是否需要跨库事务?
- 如何扩容?
- 如何迁移旧数据?
- 如何回滚?
- 如何监控全路由和慢 SQL?
23.2 开发阶段清单
- 本地准备两个真实库。
- 建完整真实表。
- 打开
sql-show。 - 写插入测试。
- 写按分片键查询测试。
- 写缺少分片键查询测试,观察是否全路由。
- 写绑定表 join 测试。
- 写广播表测试。
- 写事务回滚测试。
23.3 上线前清单
- 关闭生产
sql-show。 - 检查连接池总连接数。
- 检查真实库索引。
- 压测核心 SQL。
- 压测全路由边界。
- 验证主从延迟。
- 验证备份恢复。
- 准备回滚脚本。
- 准备路由排查手册。
- 接入监控指标。
23.4 自动化测试矩阵
| 测试类型 | 目标 |
|---|---|
| 算法单元测试 | 边界值、负数、IN、范围、非法值 |
| Mapper 测试 | 逻辑 SQL 正确、分片键不丢 |
| 路由测试 | 真实执行单元数量符合预期 |
| 集成测试 | 两库多表真实 CRUD |
| 事务测试 | 单库回滚、跨库失败边界 |
| 一致性测试 | 主从延迟、强制主库 |
| 升级回归 | SQL 兼容性和规则行为 |
| 故障测试 | 宕库、断网、连接池耗尽 |
| 性能测试 | 精确路由、IN、多分片、全路由 |
| 迁移测试 | 全量、增量、校验、回滚 |
23.5 路由断言示例
可以在测试环境采集 ShardingSphere SQL 日志,对一次请求断言:
1 | |
如果使用 Proxy,则使用 PREVIEW SQL 构建黄金路由快照。
23.6 性能基线必须分场景
不要只跑一个 QPS。至少包含:
1 | |
记录:
1 | |
23.7 上线验收门槛示例
1 | |
24. 一份推荐的生产组合
如果是 Spring Boot 3.5 的 Java 业务系统,我推荐优先:
1 | |
如果是多语言、多系统统一接入:
1 | |
24.1 不同规模下的推荐演进
flowchart LR
A[单库单表] --> B[索引与 SQL 优化]
B --> C[缓存/归档/读写分离]
C --> D[垂直拆库]
D --> E[ShardingSphere-JDBC 分片]
E --> F[Proxy/Cluster 集中治理]
F --> G[迁移、弹性与统一数据平台]
**早期:**优先单库、索引、缓存、归档。
**增长期:**读写分离、按业务垂直拆库,降低单库负担。
**大规模:**核心大表水平分片,建立分片键约束、迁移和监控。
**平台期:**多语言统一接入、Proxy 高可用、DistSQL 审计、规则中心。
24.2 一份更保守的默认选择
对于多数 Spring Boot 3.5 服务:
1 | |
先把主路径做窄、做准,再增加复杂能力。
25. 一个更现实的架构建议
分库分表不是第一步,而是系统演进到一定阶段后的手术。
在决定使用 ShardingSphere 之前,先确认是否已经做过:
- SQL 优化。
- 索引优化。
- 冷热数据拆分。
- 缓存优化。
- 读写分离。
- 垂直拆库。
- 历史数据归档。
- 报表查询隔离。
如果这些都没做,直接上分库分表,可能只是把一个慢系统升级成一个复杂的慢系统。
26. 完整生产架构示例
下面是一套相对完整但仍可渐进落地的架构:
flowchart TB
U[客户端] --> GW[API Gateway]
GW --> APP[Spring Boot 3.5 集群]
APP --> SS[ShardingSphere-JDBC 5.5.3]
SS --> G0[读写组 ds_0]
SS --> G1[读写组 ds_1]
G0 --> M0[(MySQL Master 0)]
G0 --> R00[(Replica 0-0)]
G0 --> R01[(Replica 0-1)]
G1 --> M1[(MySQL Master 1)]
G1 --> R10[(Replica 1-0)]
G1 --> R11[(Replica 1-1)]
APP --> REDIS[(Redis)]
APP --> OUTBOX[(Local Event)]
OUTBOX --> MQ[MQ]
MQ --> SEARCH[(Elasticsearch)]
MQ --> OLAP[(Doris / ClickHouse)]
SS --> OTEL[OpenTelemetry]
APP --> OTEL
OTEL --> OBS[Prometheus / Grafana / Trace]
CICD[CI/CD] --> DDL[分片 DDL 管理]
DDL --> M0
DDL --> M1
这套架构中:
- ShardingSphere 负责 OLTP 透明路由;
- Redis 只承担适合缓存的数据;
- MQ + Outbox 解决跨聚合最终一致性;
- Elasticsearch 解决多条件检索;
- Doris/ClickHouse 解决报表聚合;
- CI/CD 统一管理真实 DDL;
- 可观察平台关联业务请求与真实 SQL。
27. 从单库迁移到分库分表的实施计划
阶段 0:证明问题存在
产出:
1 | |
阶段 1:分片建模
产出:
1 | |
阶段 2:双环境验证
- 本地 Docker 双库;
- 测试环境接近生产规模;
- 兼容性回归;
- 路由快照;
- 故障演练。
阶段 3:迁移与灰度
- 全量;
- 增量;
- 校验;
- 灰度读;
- 灰度写;
- 全量切换。
阶段 4:稳定性建设
- 全路由审计;
- 慢 SQL TopN;
- 规则漂移检测;
- 定期故障演练;
- 容量复盘;
- 扩容预案。
28. 安全与合规清单
数据安全
- 敏感字段存储加密;
- 查询结果脱敏;
- 日志不打印敏感参数;
- 密钥进入 KMS/Vault;
- 备份同样加密;
- 测试环境不使用生产明文数据。
访问安全
- 应用账号最小权限;
- DistSQL 管理账号隔离;
- Proxy 业务与管理网络隔离;
- 数据库只允许指定网段;
- 定期轮换密码和证书;
- 审计规则和账号变更。
供应链安全
- 固定依赖版本;
- 校验 Apache 发布包签名;
- 扫描 Maven 依赖漏洞;
- 关注 ShardingSphere Release Notes;
- 镜像使用摘要锁定;
- SBOM 归档。
29. 性能压测方案
29.1 数据准备
压测数据必须模拟:
1 | |
只生成完全均匀数据会掩盖热点。
29.2 压测阶梯
1 | |
29.3 对照组
至少对比:
1 | |
这样才能区分数据库耗时与中间件解析、执行、归并开销。
29.4 性能报告模板
| 场景 | QPS | P50 | P95 | P99 | 路由数 | DB CPU | 连接峰值 | 错误率 |
|---|---|---|---|---|---|---|---|---|
| 点查 | 1 | |||||||
| IN 查询 | ||||||||
| 深分页 | ||||||||
| 聚合 | ||||||||
| 批量写 |
30. 故障演练清单
1 | |
每个演练都记录:
1 | |
31. 代码评审检查表
看到访问分片表的代码时,逐项问:
- SQL 是否包含分库键和分表键?
- 参数可能为 null 吗?
- IN 条件最大多少?
- 是否可能范围跨所有分片?
- 是否深分页?
- 是否跨分片排序/聚合?
- JOIN 是否绑定表?
- UPDATE/DELETE 是否精准路由?
- 事务是否跨库?
- 重试是否幂等?
- 读请求是否允许从库延迟?
- 日志是否泄露参数?
- 是否有路由单元断言?
32. 常见问答
Q1:分库分表后还能使用数据库唯一索引吗?
单个真实表内可以,但它只能保证该物理表范围内唯一。全局唯一需要:
- 全局 ID;
- 路由前置唯一性服务;
- 唯一索引表;
- 业务上让唯一值包含分片维度;
- 最终冲突检测与补偿。
Q2:能不能按时间分表,同时按用户查询?
可以,但用户跨时间范围查询会命中多张时间表。需要在“写入归档便利”和“用户查询便利”之间取舍。也可以分库按用户、分表按时间,但查询条件最好同时包含两者。
Q3:分片后能不能随便 JOIN?
不能。绑定表、广播表和同分片键 JOIN 最友好;非绑定跨分片 JOIN 需要重点验证路由组合和 SQL 支持。
Q4:为什么不推荐所有查询都用 Hint?
Hint 把路由正确性推给业务代码,容易因上下文丢失或残留导致错路由。只有分片键确实不在 SQL、且业务上下文可靠时使用。
Q5:ShardingSphere 能代替数据库代理吗?
Proxy 本身可以作为数据库网关,但 JDBC 不是通用网络代理。是否需要 Proxy 取决于语言、集中治理、运维和协议要求。
Q6:单表多大必须分片?
没有统一阈值。要结合行宽、索引、查询方式、写入量、DDL 窗口和硬件能力。一个 1 亿行只做主键点查的表,可能比一个 1000 万行复杂聚合表更轻松。
Q7:是否应该先分 1024 张表?
通常不应该。可以预留 1024 个逻辑槽位,但物理表数量要根据容量和运维能力决定。
Q8:读写分离会不会自动保证刚写数据可见?
不会。使用事务内主库、Hint 强制主库、会话粘滞或版本等待策略。
Q9:Spring Boot 3.5 能否直接使用 XA?
ShardingSphere 5.5.3 官方文档明确提示 Jakarta EE 9+/Spring Boot 3 场景下 XA 尚未就绪。不要把“能配置”当成“生产可用”。
Q10:如何判断分片是否成功?
不是只看数据插入成功,而要验证:逻辑结果、物理落点、路由数量、事务边界、故障行为和性能基线。
33. 术语表
| 术语 | 含义 |
|---|---|
| Logic Database | 应用看到的逻辑数据库 |
| Storage Unit | ShardingSphere 管理的真实数据源 |
| Logic Table | SQL 中使用的逻辑表名 |
| Actual Table | 数据库中真实存在的物理表 |
| Actual Data Node | 数据源与真实表的组合 |
| Sharding Column | 用于计算路由的字段 |
| Sharding Strategy | 提取分片值并调用算法的策略 |
| Sharding Algorithm | 将分片值映射到目标节点的算法 |
| Binding Table | 分片规则一致、可同路由 JOIN 的表组 |
| Broadcast Table | 在多个数据源保存副本的表 |
| Single Table | 被管理但不参与分片的表 |
| Route Unit | 路由生成的逻辑目标单元 |
| Execution Unit | 改写后可执行的真实 SQL 单元 |
| DistSQL | 用 SQL 管理 ShardingSphere 规则的语言 |
| Pipeline | 数据迁移和弹性相关能力 |
| Hint | 由业务上下文显式指定路由 |
| Merge | 把多分片结果合成为逻辑结果 |
34. 最终上线检查清单
架构
- 已证明单库优化不足;
- 分片键有真实 SQL 数据支撑;
- 绑定表、广播表、单表清单明确;
- 管理端复杂查询有独立方案;
- 扩容和回滚方案已评审。
配置
- 锁定 ShardingSphere 5.5.3;
- 未混入旧 Starter;
- 每个真实数据源连接池已预算;
- 生产密码不在 Git;
- 元数据检查已验证;
- SQL 日志生产默认关闭。
SQL
- 核心查询精准路由;
- UPDATE/DELETE 带分片键;
- 无不可控深分页;
- 聚合 SQL 有压测;
- JOIN 已验证绑定关系;
- ORM 动态 SQL 不会丢分片键。
事务与一致性
- 跨库事务边界清楚;
- LOCAL 不被误当强一致;
- 读己之写策略明确;
- 消息和补偿幂等;
- 对账任务已实现;
- 故障演练通过。
运维
- 所有真实表结构一致;
- DDL 发布流程明确;
- 迁移全量/增量/校验可观测;
- 规则指纹和漂移告警已建立;
- 全路由、连接池、主从延迟有告警;
- 备份恢复已演练。
35. 结论
ShardingSphere 的价值不是让应用“感觉不到分库分表”,而是把大量重复的 SQL 解析、路由、改写、执行和归并能力标准化。透明只存在于 API 表面,架构复杂度不会凭空消失。
真正高质量的落地有几个共同点:
- 分片键来自访问模式,而不是来自表字段里“看起来像 ID”的那一列;
- 核心 SQL 默认精准路由,全路由是被识别、限制和监控的例外;
- 事务边界通过业务聚合设计收敛,跨库一致性有消息、补偿和对账;
- 扩容从第一天就考虑逻辑槽位、迁移、灰度和回滚;
- 监控不仅看数据库 CPU,还看路由数、执行单元、连接池等待和归并耗时;
- 每次规则、版本和 DDL 变化都经过自动化回归。
分库分表最怕的不是复杂,而是复杂却不可见。把规则、路由、事务和迁移全部变成可验证的工程对象,ShardingSphere 才会真正成为基础设施,而不是新的不确定性来源。
参考资料
- 用户指定参考文章:掘金
https://juejin.cn/post/7186845167989555256#heading-54 - Apache ShardingSphere 5.5.3 Release Notes
https://github.com/apache/shardingsphere/blob/5.5.3/RELEASE-NOTES.md - Apache ShardingSphere 5.5.3 Route Engine
https://shardingsphere.apache.org/document/5.5.3/en/reference/sharding/route/ - Apache ShardingSphere 5.5.3 Rewrite Engine
https://shardingsphere.apache.org/document/5.5.3/en/reference/sharding/rewrite/ - Apache ShardingSphere 5.5.3 Execute Engine
https://shardingsphere.apache.org/document/5.5.3/en/reference/sharding/execute/ - Apache ShardingSphere 5.5.3 Merger Engine
https://shardingsphere.apache.org/document/5.5.3/en/reference/sharding/merge/ - Apache ShardingSphere Transaction Limitations
https://shardingsphere.apache.org/document/current/en/features/transaction/limitations/ - Apache ShardingSphere Observability
https://shardingsphere.apache.org/document/current/en/reference/observability/ - Apache ShardingSphere-Agent
https://shardingsphere.apache.org/document/current/en/user-manual/shardingsphere-agent/ - Apache ShardingSphere 官方网站
https://shardingsphere.apache.org/ - Apache ShardingSphere 5.5.3 Downloads
https://shardingsphere.apache.org/document/current/en/downloads/ - Apache ShardingSphere Overview
https://shardingsphere.apache.org/document/current/en/overview/ - ShardingSphere-JDBC Spring Boot Driver 配置
https://shardingsphere.apache.org/document/5.5.3/en/user-manual/shardingsphere-jdbc/yaml-config/jdbc-driver/spring-boot/ - ShardingSphere-JDBC YAML 配置
https://shardingsphere.apache.org/document/5.5.3/cn/user-manual/shardingsphere-jdbc/yaml-config/ - ShardingSphere-JDBC 数据源配置
https://shardingsphere.apache.org/document/5.5.3/cn/user-manual/shardingsphere-jdbc/yaml-config/data-source/ - ShardingSphere-JDBC 数据分片 YAML 配置
https://shardingsphere.apache.org/document/5.5.3/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/sharding/ - ShardingSphere-JDBC 读写分离 YAML 配置
https://shardingsphere.apache.org/document/5.5.3/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/readwrite-splitting/ - ShardingSphere-JDBC 数据加密 YAML 配置
https://shardingsphere.apache.org/document/5.5.3/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/encrypt/ - ShardingSphere-JDBC 数据脱敏 YAML 配置
https://shardingsphere.apache.org/document/5.5.3/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/mask/ - ShardingSphere-JDBC 影子库 YAML 配置
https://shardingsphere.apache.org/document/5.5.3/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/shadow/ - ShardingSphere 分片算法
https://shardingsphere.apache.org/document/5.5.3/cn/user-manual/common-config/builtin-algorithm/sharding/ - ShardingSphere 分布式序列算法
https://shardingsphere.apache.org/document/5.5.3/cn/user-manual/common-config/builtin-algorithm/keygen/ - ShardingSphere-Proxy 快速入门
https://shardingsphere.apache.org/document/current/en/quick-start/shardingsphere-proxy-quick-start/ - DistSQL REGISTER STORAGE UNIT
https://shardingsphere.apache.org/document/5.5.3/cn/user-manual/shardingsphere-proxy/distsql/syntax/rdl/storage-unit-definition/register-storage-unit/ - DistSQL CREATE SHARDING TABLE RULE
https://shardingsphere.apache.org/document/5.5.3/cn/user-manual/shardingsphere-proxy/distsql/syntax/rdl/rule-definition/sharding/create-sharding-table-rule/
启示录
分库分表不是为了证明架构复杂,而是为了让数据在增长之后仍然可控。
好的架构不是把所有功能都堆上去,而是在每一个功能背后都知道自己为什么需要它、什么时候不用它、出了问题如何收回来。
富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。