ShardingSphere-jdbc 5.5.0 Spring Boot 基础配置实战先说重点这个配置我做过多遍每次从旧版本升级到5.5.0都要踩一遍配置结构变化的坑。ShardingSphere 5.x 和 4.x 的配置方式完全不是一回事如果你是从 4.x 时代过来的会发现原来的spring.shardingsphere.datasource.names这类前缀全部失效5.5.0 直接走独立的application-shardingsphere.yaml或者放在 Spring Boot 配置里的shardingsphere.rules结构。如果到了这一步还按照老文档抄启动大概率直接报错。这篇文章面向的是需要快速上手的人你的服务已经跑着 Spring Boot 2.7 或 3.x现在需要接入分库分表不想被各种网上的碎片化文档折磨。我会把 5.5.0 版本的基础配置完整拆开从依赖引入、数据源配置、分片规则、分布式主键到常用的读写分离配置再到实际使用中的排查思路整个过程照着走一遍基本就能跑通。1. 版本选型与环境准备ShardingSphere-jdbc 本质上就是一个对 javax.sql.DataSource 的增强实现。它不改变你业务代码里的 JDBC 调用方式也不要求你必须用某个 ORM 框架MyBatis、MyBatis-Plus、Hibernate、JdbcTemplate 都能无缝对接。这意味着你的项目改造工作量集中在配置层而不是代码层这也是它相比其他分库分表方案最大的优势。1.1 依赖引入与版本对应关系5.5.0 这个版本比较特殊它同时兼顾了 Spring Boot 2.x 和 3.x 的生态。但这里有个关键细节Spring Boot 3.x 基于 Jakarta EE而 Spring Boot 2.x 使用的还是 javax。dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core/artifactId version5.5.0/version /dependency如果不引入shardingsphere-jdbc-core而只引入 starter在 Spring Boot 3.x 环境中会面临自动配置类无法加载的问题。从 5.4.0 开始官方对 Spring Boot 3 的支持已经比较完整但为了稳妥我建议核心包和 starter 一起引入。dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core/artifactId version5.5.0/version /dependency dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-spring-boot-starter/artifactId version5.5.0/version /dependency如果你的项目还在用 Spring Boot 2.3 或更早版本那就不要把 5.5.0 硬塞进去挑 5.2.x 或者 5.3.x 反而更省事。版本匹配是一个组合问题不是越新越好。我用 5.5.0 搭配 Spring Boot 2.7.18 做了长时间的稳定性验证没有发现明显的兼容性问题这个组合比较推荐。注意不要同时引入shardingsphere-jdbc-spring-boot-starter和其他数据源 starter如druid-spring-boot-starter而不做排除处理。Starter 之间会自动竞争 DataSource Bean极容易造成路由失效或启动循环依赖。1.2 压测选型什么场景下你需要它在开始配之前先判断你的项目是否真的需要引入分库分表组件。我见过不少团队业务量连单库单表都没跑满就急急忙忙引入了分库分表结果事务从本地事务升级成了分布式事务性能反而下降了。ShardingSphere-jdbc 的核心价值体现在两个场景单表数据量超过千万级索引深了写入和查询都开始出现明显的延迟。业务有明显的分片维度比如订单按用户 ID 分片、日志按时间分片、消息按业务线分片。如果你的场景不属于这两类那建议先做其他优化比如索引优化、读写分离、缓存降级。分库分表解决的是数据容量和并发写入瓶颈不是慢查询的万能药。另外5.5.0 的代码已经在内核层做了很多优化比如对 SQL 解析阶段的缓存机制做了增强在相同配置下解析耗时相比 5.2.x 降低了约 30%。但对于那种只查询一天数据且很少跨库 join 的报表类系统单库单表加索引可能已经够了没必要增加运维复杂度。2. 核心配置文件拆解从数据源到规则5.5.0 的配置模型非常清晰由dataSources数据源、rules规则、props属性三段组成。三段缺一不可而且规则的类型和顺序是有讲究的。下面我给出一个包含了分库分表、读写分离、分布式主键的完整最小配置后面再逐步拆开讲。2.1 一个可以直接跑通的基础配置spring: shardingsphere: enabled: true datasource: names: ds-master-0,ds-master-0-slave-0,ds-master-1,ds-master-1-slave-0 ds-master-0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://172.20.10.3:3306/order_db_0?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root123 ds-master-0-slave-0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://172.20.10.4:3306/order_db_0?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root123 ds-master-1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://172.20.10.3:3306/order_db_1?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root123 ds-master-1-slave-0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://172.20.10.4:3306/order_db_1?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root123 rules: sharding: binding-tables: - t_order,t_order_item broadcast-tables: - t_dict default-database-strategy: standard: sharding-column: tenant_id sharding-algorithm-name: database-inline tables: t_order: actual-data-nodes: ds-$-{0..1}.t_order_$-{0..1} database-strategy: standard: sharding-column: user_id sharding-algorithm-name: database-inline table-strategy: standard: sharding-column: user_id sharding-algorithm-name: table-inline keyGenerateStrategy: column: id key-generator-name: snowflake t_order_item: actual-data-nodes: ds-$-{0..1}.t_order_item_$-{0..1} database-strategy: standard: sharding-column: user_id sharding-algorithm-name: database-inline table-strategy: standard: sharding-column: user_id sharding-algorithm-name: table-inline keyGenerateStrategy: column: id key-generator-name: snowflake key-generators: snowflake: type: SNOWFLAKE props: worker-id: 1 sharding-algorithms: database-inline: type: INLINE props: algorithm-expression: ds-$-{user_id % 2} allow-range-query-with-inline-sharding: true table-inline: type: INLINE props: algorithm-expression: t_order_$-{user_id % 2} allow-range-query-with-inline-sharding: true readwrite-split: >algorithm-expression: ds-$-{user_id % 2}别小看这一点我当年排查这个问题的姿势是每个表的数据都在但分布不对一半数据路由到了一个不存在的逻辑库名上。后来看解析日志才发现是占位符冲突。还有一个容易被忽略的参数allow-range-query-with-inline-sharding。它的含义是是否允许在分片键上执行范围查询。默认值是 false也就是说你会遇到一条 SQL 查询WHERE user_id BETWEEN 1 AND 100直接被拒绝执行或路由结果报错。如果需要范围内查询需要显式设置这个参数为 true但注意 INLINE 分片不支持范围路由优化开启后依然会全库全表扫描性能上要有预期。3.2 分片策略选型默认策略与表策略的关系配置中有两处策略default-database-strategy默认库策略和tables下的database-strategy表级库策略。它们的优先级是表级策略 默认策略。如果某个表没有单独配置库策略就用默认配置如果配置了就用表级的。我用这个机制把大多数表分片的相同规则收敛到默认策略集中管理同时保留个别表走独立策略的灵活性。分片策略有两种standard标准策略和complex复合策略。标准策略仅支持单分片键的 SQL 路由复合策略支持多个分片键参与路由计算。如果将来有按user_id time双键分片的需求要在这里选择complex并配置sharding-algorithm-name指向COMPLEX_INLINE或自定义类实现。注意标准策略不支持跨分片键的关联查询若强制执行单 SQL 跨多分片查询ShardingSphere 会执行全路由full-route扫描所有分片结果是数据对但性能会明显下降需要提前做好评估。3.3 分片键选择的核心原则分片键决定了数据分布均匀性和查询命中率。选择分片键的核心原则有两层第一层尽量选业务天然分布均匀的字段比如用户 ID。不要选状态字段只有几个值或时间字段如果业务有热点时段会产生数据倾斜。第二层分片键必须能覆盖主要查询条件。如果还有大量查询不是按分片键走的要么补充二级索引表可以用广播表实现要么考虑用复合分片键去覆盖高频查询。如果经常做SELECT * FROM t_order WHERE order_no xxx而分片键是user_id那这个查询只能全路由效率极低。解决办法是额外建一张t_order_no_mapping表把order_no和user_id的映射关系存下来先查映射表拿到user_id再路由实际订单表。3.4 自定义分片算法分片算法支持嵌入 Java 代码来定义。如果内置的 INLINE 满足不了复杂业务比如按月份分表、按地域分片可以在 Java 类中实现StandardShardingAlgorithmT接口然后在 YAML 中通过type: CLASS_BASED注册。public class UserIdMonthShardingAlgorithm implements StandardShardingAlgorithmString { Override public String doSharding(final CollectionString availableTargetNames, final PreciseShardingValueString shardingValue) { String value shardingValue.getValue(); String target t_order_ value.substring(0, 6); if (availableTargetNames.contains(target)) { return target; } throw new UnsupportedOperationException(路由目标不存在: target); } Override public CollectionString doSharding(final CollectionString availableTargetNames, final RangeShardingValueString shardingValue) { CollectionString result new LinkedList(); for (String target : availableTargetNames) { result.add(target); } return result; } Override public void init() { } Override public String getType() { return USER_ID_MONTH; } }YAML 中注册sharding-algorithms: user-id-month: type: CLASS_BASED props: strategy: standard algorithmClassName: com.example.sharding.UserIdMonthShardingAlgorithm这里有两个注意点getType()返回值 YAML 中对应的 type 是什么就填什么。我这里返回的是USER_ID_MONTHYAML 里的type就写CLASS_BASED固定值然后通过algorithmClassName找到你的类。范围查询的doSharding不要偷懒返回空集否则BETWEEN查询会直接报错。最稳妥的实现是返回所有可行目标交给 ShardingSphere 在内部进行归并。4. 分布式主键与绑定表、广播表配置分片之后自增主键的全局唯一性保证就不能再依赖数据库自增了。ShardingSphere 提供了分布式主键生成器默认是雪花算法SNOWFLAKE实现。4.1 雪花算法的 worker-id 与时钟回拨问题雪花算法生成的 ID 由时间戳、工作节点、序列号三部分组成。在 5.5.0 中worker-id默认值是从配置中心拉取或者使用本机 IP 取模计算但多个应用实例如果没有确切指定可能会生成相同的 workerId导致 ID 冲突。实践上我会给每个实例显式配置key-generators: snowflake: type: SNOWFLAKE props: worker-id: 1 max-tolerate-time-difference-milliseconds: 10max-tolerate-time-difference-milliseconds这个参数用于容忍主机之间的时钟偏移。如果超过这个阈值生成时会抛异常而不是生成错误 ID这对订单类、支付类系统的可追溯性非常重要。注意主键字段在数据库里的类型必须是 BIGINT。用 varchar 存雪花 ID 也可以但索引和存储效率会降低。生成策略在 YAML 中通过keyGenerateStrategy指定后应用层不需要改动ShardingSphere 会自动填充主键字段。4.2 绑定表的配置意义与 join 性能优化绑定表的概念逻辑上具有相同分片规则的一组表比如t_order和t_order_item都按user_id分片且分片数一致。如果不配置绑定关系当执行两表 join 时会先做笛卡尔积路由2 个分片库 × 2 个分片表 4 个分组 SQL全部并行执行再在内存中归并性能很低。配置绑定表后ShardingSphere 会保证同一user_id的数据在t_order和t_order_item中落到相同的库和表join 时路由到同库同分片只需执行 2 条 SQL。绑定表配置本身没有任何副作用只要分片规则确实一致建议直接把所有有 join 关系的表全部绑上。4.3 广播表的使用边界广播表对应每个库中都存在的相同结构小表典型代表是字典表、配置表。它会把写操作广播到所有分片库查询时从任意一个分片拉取。但广播表不是免费的每次写入都会放大到所有分片如果字典表经常更新且数据量大写放大效应会很严重。跨库事务会存在一致性问题需要额外的补偿机制。所以广播表只适合低频写入、数据量小的场景。常见做法是字典表在应用启动时加载到本地缓存Caffeine数据库只作为持久化写入频率极低这样广播表的写放大影响可以忽略。4.4 分布式 ID 生成器在实际使用中的几个问题雪花 ID 看起来只是主键但生产上碰过几个很有意思的问题列出来供参考长整型精度溢出尤其是前端 JavaScript 无法精确表示 2^53 以上的整数。后端插入正常前端拿到 ID 后会出现末几位全部是 0。解决方式是接口返回时转成字符串。用雪花 ID 做分页排序时因为 ID 不是连续的页面上依赖 ID 作为游标做增量查询是可行的但注意 ID 的大小关系只反映创建时刻的时间顺序。某些 ORM 框架的 ID 回填机制和 ShardingSphere 的 keyGenerateStrategy 有冲突。典型表现你配置了主键生成策略但业务代码里手动 set 了 IDShardingSphere 会忽略你的 set 值直接用雪花生成导致数据错乱。解决方案是只要用了 keyGenerateStrategy业务代码一律不要主动设置主键。5. 读写分离与分片规则的联动配置5.5.0 的读写分离规则在上一版基础上有一些变化需要注意优先级readwrite-split规则建立在物理数据源之上sharding规则再建立在readwrite-split的逻辑数据源之上。换言之配置的层级是物理数据源 → 读写分离逻辑数据源 → 分片逻辑数据源。5.1 读写分离的数据源命名规范在上面的配置中可以看到我把物理库定义为ds-master-0、ds-master-0-slave-0、ds-master-1、ds-master-1-slave-0然后定义了两个 readwrite-split 逻辑数据源order_readwrite_0和order_readwrite_1。如果此时分片规则里的actual-data-nodes写的是ds-$-{0..1}.t_order_$-{0..1}那路由处理时会优先找 readwrite-split 逻辑数据源而不是最底层的物理数据源。命名上一定要保持逻辑清晰不要出现像ds-master-0既是物理数据源又被用于逻辑引用这种混乱。推荐命名规则物理写库w0、w1物理读库r0-0、r1-0读写分离逻辑库rw0、rw1分片配置引用的就是rw0、rw1这样一来配置里看到actual-data-nodes: rw$-{0..1}.t_order_$-{0..1}一眼就知道走的是读写分离逻辑。5.2 同库同表场景的数据一致性风险提示读写分离的数据一致性由主从复制保证ShardingSphere 本身不参与复制链路。如果主从延迟严重读写分离后立刻读从库会读到旧数据。有两个应对思路强制路由主库。ShardingSphere 提供了 Hint 机制可以设置HintManager.getInstance().setWriteRouteOnly()但只适合特定读多写少的场景不要全局开启。配置主从延迟阈值。5.5.0 支持readwrite-split的负载均衡算法可以选择ROUND_ROBIN或RANDOM但无法做到基于延迟的动态感知。高一致性要求的查询建议还是走主库。5.3 多从库负载均衡算法配置当读数据源配置了多个时可以指定负载均衡策略readwrite-split: >