资讯动态

ShardingJDBC 5.x分库分表YAML配置详解:从数据源到分片算法

发布时间:2026/10/16 17:46:12 来源:尧图企业网站定制
很多做分库分表的朋友第一反应都是先去找ShardingJDBC的文档结果被一堆概念和配置项劝退。这个系列第二篇我直接讲最落地的东西基于yaml的基本配置。t_order按user_id分库、按order_id分表从数据源定义到分片算法一步步说清楚每段配置到底在干什么。这篇适合刚接触ShardingJDBC的Java开发也适合已经跑通但想系统理清配置结构的人。先说清楚本文示例基于ShardingJDBC 5.x版本具体以5.4.x为准和4.x的配置结构差异比较大后文会专门把版本坑列出来。你手里的依赖如果还是4.x配置路径完全不同先确认版本再对照操作。1. 为什么选择yaml配置方式1.1 yaml vs properties从可维护性角度选型ShardingJDBC支持两种配置载体 properties文件和yaml文件。我见过很多老项目用properties写分片规则十几行还能忍一旦涉及复合分片、绑定表、分布式ID生成器properties的扁平结构会让你崩溃。spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-columnuser_id这种长键写到第8层少一个前缀就全盘不生效。yaml的优势在于天然支持层级嵌套分片规则本身就是树形结构用yaml表达非常直观。而且SpringBoot 2.4之后对yaml的加载机制更友好配置刷新、profile切换都比properties顺滑。所以新项目我无条件推荐yaml老项目迁移配置也只是体力活。还有一个现实原因ShardingJDBC官方文档、社区帖子、源码注释里的示例代码绝大多数是yaml格式ShardingSphere 5.x原生配置就是yaml风格。跟官方示例保持一致排查问题的时候对照文档、搜社区更省力。1.2 配置结构拆解从整体视角理解五个核心块一份完整的ShardingJDBC yaml配置顶层是spring.shardingsphere注意5.x是这个名字4.x是spring.shardingsphere没错但3.x是spring.shardingsphere.datasource开头写错前缀配置静默失效下面挂四部分内容datasource数据源集合定义每个物理库的连接信息。rules规则集合分片规则sharding、读写分离readwrite-splitting、加密encrypt都在这下面。props全局属性比如是否打印SQL、允许跨库查询开关。mode运行模式单机模式用memory默认集群模式用cluster5.x里这个配置很重要但默认值够用。我推荐的阅读顺序是先看datasource理解有多少个库再看rules.sharding.tables理解有哪些表要分片、按什么分然后看algorithms理解分片算法是怎么定义的最后看props打开SQL调试。这套结构里最容易混淆的是actual-data-nodes。很多人把它理解成表名规则但ds$-{0..1}.t_order_$-{0..1}的完整含义是数据源ds0和ds1里各有t_order_0和t_order_1两张物理表一共4张物理表参与路由。这不是模糊匹配是精确的节点清单。2. 核心配置项逐一拆解2.1 数据源定义类型、连接参数和默认源的坑先定义一个最标准的双数据源场景spring: shardingsphere: datasource: names: ds0, ds1 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/ds0?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/ds1?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root注意两个细节。第一names必须和后面每个数据源的key完全一致多写一个、少写一个启动时直接报datasource name not found。第二jdbc-url而不是urlShardingJDBC要求连真实库时用jdbc-url因为它要拿这个参数包装自己的DataSource写url在某些版本里不识别。我建议至少配置一个default-data-source。虽然分片规则明确下大部分SQL都能命中路由但总有例外——比如某条SQL只查广播表或者某个事务里临时跨库操作。不设置默认源这些SQL直接报Can not find a data source。虽然有人说默认源就是第一个数据源但显式配置比隐式猜测可靠spring: shardingsphere: datasource: names: ds0, ds1 default-data-source-name: ds0还有一个HikariCP的细节如果业务量不大别在yaml里写死maximum-pool-size之类的参数让连接池用默认值。我第一次就写了minimum-idle: 2结果并发一上来连接池要动态扩容反而增加延迟。小项目就别做这种预优化了。2.2 分片规则分片键、策略和算法三者如何配合分片规则是ShardingJDBC的核心先看一个完整的表配置spring: shardingsphere: rules: sharding: tables: t_order: actual-data-nodes: ds$-{0..1}.t_order_$-{0..1} database-strategy: standard: sharding-column: user_id sharding-algorithm-name: db_inline table-strategy: standard: sharding-column: order_id sharding-algorithm-name: table_inline key-generate-strategy: column: order_id key-generator-name: snowflake这段配置讲了三件事这张表落在哪些物理节点actual-data-nodes按user_id算库、按order_id算表两个strategyorder_id由雪花算法生成key-generate-strategy。很多人写分片键时习惯使用userId而不是user_id注意ShardingJDBC的分片键必须对应数据库表的真实列名。如果你的实体里是userId、表里是user_id这里一定要写user_id。database-strategy和table-strategy都用了standard类型这意味着分片键是单列且操作符只支持和IN。如果你要按非分片键查询ShardingJDBC会全路由扫描所有分片性能问题先不说至少不会报错——但要小心跨库联合查询子查询的兼容性问题。2.3 分片算法定义inline表达式是最常用的入门算法分片策略引用了算法名字算法本身在sharding-algorithms节点里定义spring: shardingsphere: rules: sharding: sharding-algorithms: db_inline: type: INLINE props: algorithm-expression: ds$-{user_id % 2} table_inline: type: INLINE props: algorithm-expression: t_order_$-{order_id % 2}INLINE算法就是Groovy表达式求值。ds$-{user_id % 2}的意思是取分片键user_id的值求余2得到0或1拼出ds0或ds1。这是最简单的取模分片。初次上手用取模一定比用HASH_MOD更容易理解而且user_id如果本身就是数字类型比如BIGINT取模结果稳定、无哈希碰撞。但有几个很重要的边界表达式里的user_id是列名Groovy表达式上下文会注入当前SQL里该列的值不能写成#{user_id}或:user_id。如果分片键在SQL里没出现这个算法不会执行SQL会走全路由。$-{...}这个写法是ShardingSphere的约定不是Groovy的一部分不要随意改成${...}——那会被Spring误认为占位符配置shardingsphere会直接不加载规则。调试inline算法的经验是先在props里临时加一个键值对例如allow-range-query-with-inline-sharding: true否则执行BETWEEN或操作符时inline算法默认不支持下推会直接报错。这个配置在生产环境很有用但要注意全路由的性能风险。2.4 绑定表、广播表、雪花算法和props属性分库分表不只是单表拆分表之间还有关联关系。在分片环境下JOIN查询如果两张表的分片键一致但ShardingJDBC不知道这件事它就只能采用笛卡尔积路由性能极其糟糕。用绑定表告诉ShardingJDBC主表和子表的分片键是一致的可以按相同规则路由spring: shardingsphere: rules: sharding: binding-tables: - t_order, t_order_itemt_order和t_order_item必须用完全相同的分片键和分片算法绑定表才有意义。否则比如t_order按user_id分、t_order_item按user_id分算法不同绑定也无法保证一起落库。广播表的场景更特殊。字典表、配置表这种所有分片库都要完整保留一份的小表定义为广播表后ShardingJDBC在所有数据源上执行同样的写操作读操作取任意一个节点即可spring: shardingsphere: rules: sharding: broadcast-tables: - t_dict生成分布式ID我直接用内置雪花算法spring: shardingsphere: rules: sharding: key-generators: snowflake: type: SNOWFLAKE props: worker-id: 1worker-id在多实例部署时一定要配。分布式环境下两个实例如果都用默认worker-id极端情况下会生成相同ID虽然概率非常低但一旦主键冲突排查成本远高于写一行配置。我用固定worker-id而不是自动生成是因为测试环境可以确定性地复现问题。props里最常用的两个参数spring: shardingsphere: props: sql-show: true sql-simple: falsesql-show: true能把实际路由到哪个库哪张表的SQL打印出来排查问题的时候第一时间打开它。生产环境必须关掉日志量太大了。sql-simple控制是否打印简单格式的SQL方便日志系统解析但默认关就行。3. 一个可跑通的最小分库分表示例3.1 依赖和环境准备我用的依赖版本组合parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core-spring-boot-starter/artifactId version5.4.1/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency这里有个大坑如果你的项目是SpringBoot 3.x而依赖用的ShardingJDBC版本还是5.2或更低启动时会因为javax.sql包和jakarta包冲突直接报错。SpringBoot 3.x必须配ShardingJDBC 5.4.0以上。我用SpringBoot 2.7.18配5.4.1是官方测试相对稳定的组合。数据库层面准备两个MySQL实例或同一实例的两个库ds0和ds1。每个库里手工建两张物理表t_order_0、t_order_1、t_order_item_0、t_order_item_1。ShardingJDBC不会自动建表这点一定记清楚。3.2 建表脚本和基础实体建表脚本CREATE TABLE t_order_0 ( order_id bigint(20) NOT NULL, user_id bigint(20) DEFAULT NULL, status varchar(32) DEFAULT NULL, PRIMARY KEY (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;我的习惯是每张分片表先建壳表跑通后自动刷表结构再调整索引。分片键user_id一定要建普通索引否则查主表时全表扫描。实体和Mapper用最直接的MyBatis写法public class Order { private Long orderId; private Long userId; private String status; }Mapper public interface OrderMapper { Insert(INSERT INTO t_order(order_id, user_id, status) VALUES(#{orderId}, #{userId}, #{status})) int insert(Order order); Select(SELECT * FROM t_order WHERE user_id #{userId} AND order_id #{orderId}) Order selectById(Param(userId) Long userId, Param(orderId) Long orderId); }这里注意SQL里写的表名是t_order不是t_order_0或者t_order_1。ShardingJDBC在SQL执行时会根据分片规则把逻辑表名改写成物理表名。如果你在SQL里写了物理表名分片直接失效。实体里插入时不需要手动set orderId让ShardingJDBC的雪花生成器赋值。但MyBatis的Insert默认不返回自增主键需要额外配置useGeneratedKeys或者在Service层插入前主动调用ID生成器我图省事就等ShardingJDBC在拦截器层面统一生成接口只接收userId和status。3.3 完整yaml配置清单把前面所有片段汇总成一份可直接复制的配置spring: shardingsphere: datasource: names: ds0, ds1 default-data-source-name: ds0 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/ds0?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/ds1?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root rules: sharding: tables: t_order: actual-data-nodes: ds$-{0..1}.t_order_$-{0..1} database-strategy: standard: sharding-column: user_id sharding-algorithm-name: db_inline table-strategy: standard: sharding-column: order_id sharding-algorithm-name: table_inline key-generate-strategy: column: order_id key-generator-name: snowflake binding-tables: - t_order, t_order_item broadcast-tables: - t_dict sharding-algorithms: db_inline: type: INLINE props: algorithm-expression: ds$-{user_id % 2} table_inline: type: INLINE props: algorithm-expression: t_order_$-{order_id % 2} key-generators: snowflake: type: SNOWFLAKE props: worker-id: 1 props: sql-show: true这份配置里actual-data-nodes里的$-{0..1}是ShardingSphere内建语法不是Spring表达式所以不会被外层Spring解析。如果这两个库不在同一实例跨库的广播表写入会要求binlog开启依赖建议先用同一实例两个库做demo省去跨库事务的配置成本。这个选择是为了快速跑通生产再上distributed transaction。3.4 验证配置是否生效启动SpringBoot应用后观察控制台日志。如果看到类似[INFO] ShardingSphere: ShardingSphere 5.4.1 ... [INFO] ShardingSphere: MetaData reload ... [INFO] ShardingSphere: Loading ...说明ShardingJDBC已经加载成功。执行一个Insert操作开启sql-show后日志会输出Actual SQL: ds0 ::: INSERT INTO t_order_1(order_id, user_id, status) VALUES (..., ..., ...)这行输出说明user_id求余后路由到了ds0order_id求余后路由到了t_order_1。路由逻辑完全符合配置配置生效。先用一个简单的查询验证路由正确性Order order orderMapper.selectById(3L, 12345L);如果只传user_id或只传order_id配置的分片键无法获取到完整值就会触发全路由扫描日志里出现ds0和ds1两个数据源都执行了SQL。这在数据量小的时候无感但生产环境要尽量避免。4. 常见配置问题与排查实录4.1 yaml配置未生效日志中看不到Sharding相关信息遇到这个问题的第一反应就是看依赖版本和配置前缀。如果SpringBoot正常启动、不报错但日志里完全没有ShardingSphere的加载信息先检查pom.xml是否真的引入了shardingsphere-jdbc-core-spring-boot-starter。我遇到一次实际项目里同时引入了sharding-jdbc-spring-boot-starter4.x的旧包名和shardingsphere-jdbc-core-spring-boot-starter5.x新包名两个包冲突后规则全部失效启动还不报错——这个问题极其隐蔽。还有一种情况yml文件用了Tab缩进。yaml文件严格禁止Tab缩进一个Tab出现在行首解析器会静默忽略后面的内容配置看起来在但实际未加载。用IDE的yaml插件格式化一下能解决好多次配置不生效的玄学问题。再仔细检查前缀。5.x版配置前缀一定是spring.shardingsphere4.x版里用的是spring.shardingsphere但内部结构不同如果从网上抄了4.x的配置塞到5.x里只有一部分配置被识别很多规则静默被忽略。一般建议把SQL日志打开看是否有关键字缺失。排查步骤可以固定下来先看启动日志有没有ShardingSphere Banner再看DataSource是否被代理最后看执行SQL时日志会不会打印Actual SQL。三步走完大部分配置问题都浮出水面。4.2 数据源循环依赖或初始化失败The dependencies of some of the beans in the application context form a cycle这个报错在ShardingJDBC集成旧版本时很常见。报错原因是ShardingJDBC 5.x会生成一个ShardingSphereDataSource它包装了原始数据源而Spring容器中同时存在原始数据源和代理数据源导致依赖注入循环。解决方案除了升级到推荐版本5.4.x已经修复大部分场景还可以在配置里显式指定使用哪个数据源spring: shardingsphere: mode: type: Memory内存模式下ShardingJDBC不依赖外部存储元数据全部元数据在启动时从真实库读取后放内存一般不产生循环依赖。如果还报错检查是否有自定义的DataSource配置类比如在Configuration里手动new了一个DataSource这会把ShardingJDBC的代理数据源和原生数据源搞混。另外如果你的数据源用了Druid记得核对Druid版本和ShardingJDBC兼容列表。Druid 1.2.6以下的版本和ShardingJDBC 5.x有兼容问题表现是连接池初始化成功后校验失败报错信息像极了数据库连接配置错误实际是Druid内部Filter和ShardingSphere冲突。换HikariCP能避免90%这种问题。4.3 inline表达式不生效SQL执行报错找不到表inline表达式是运行时Groovy求值写法上有一点疏漏就会让ShardingJDBC路由失败。最容易犯的错误是algorithm-expression: ds${user_id % 2}把$-{...}写成了${...}。这种写法会被属性解析器先处理可能直接替换为空字符串最终表达式变成ds路由全部到ds这个不存在的库。还有分片键类型不匹配user_id在数据库是VARCHAR但存储的是数字字符串取模时Groovy会尝试转Number但某些版本的表达式引擎对字符串取模会抛异常。我的经验是分片键在业务设计上尽量用BIGINT或INT不要用字符串。如果执行查询时遇到Table ds0.t_order doesnt exist说明WHERE条件里没有分片键ShardingJDBC回退到全路由但SQL里又写了t_order这个逻辑表名某些条件下它没法正确改写。解决思路是确保查询条件必带分片键这是使用ShardingJDBC的原则。4.4 分页查询LIMIT被限制为5000条够用吗ShardingJDBC默认在分页查询时会做内存合并合并前会限制LIMIT的最大值。官方默认是max-connections-size-per-query控制连接数但很多人踩的是另一个坑查询LIMIT 100000, 20这种深分页ShardingJDBC会把每个分片都查一遍然后合并性能差且容易被限制。我建议的第一步是在props里调大max-connections-size-per-queryspring: shardingsphere: props: sql-show: true max-connections-size-per-query: 8这个参数控制每个查询对单个分片的最大连接数调大能提升并行度但不要无脑调。连接数上限不去控制分片查询时会占满连接池业务高峰时反而拖垮其他请求。深分页优化更要靠业务手段用上次查询的最后一条order_id作为游标条件加一个order_id #{lastId} ORDER BY order_id LIMIT 20这比OFFSET深分页快几个数量级。分库分表场景下OFFSET深分页基本是无解的只能业务规避。4.5 版本迁移时常见的配置错位很多团队从4.x升级5.x最容易踩的错位点配置项4.x写法5.x写法数据源keydatasource.ds0.urldatasource.ds0.jdbc-url表规则tables.t_order.actualDataNodestables.t_order.actual-data-nodes分片算法位置sharding.defaultDatabaseStrategyrules.sharding.sharding-algorithms广播表broadcastTablesrules.sharding.broadcast-tables打印SQLprops.sql.showprops.sql-show看着只是命名习惯变化但5.x把策略和算法拆成了独立定义4.x里一个defaultDatabaseStrategy搞定的事5.x必须先定义sharding-algorithms再用sharding-algorithm-name引用。如果只改前缀不重构结构配置大概率不生效。我的建议是升级时做一次配置重写不要做文本级迁移。把表路由、算法、绑定关系列个清单照着5.x的结构重新组织比人肉改键值命名要稳得多。5. 从yaml配置到分片逻辑的完整认知收口配置写到这里你自己应该能回答这几个问题了我的逻辑表对应哪些物理节点我的分片键是什么算法怎么定义全路由和精准路由的区别是什么绑定表和广播表解决了什么问题。这个认知比背下来配置项更重要。从我实战的感受来说yaml配置本身并不难难的是对每个配置背后的路由逻辑有清晰的感知。刚上手时不要追求配置覆盖所有场景先跑通最小案例再去处理绑定表、广播表和分布式ID。分片键选错、表达式写错、版本迁移错位这三大坑我都踩过每次都是靠打开sql-show看实际路由到的SQL一步步定位的。下一篇我会讲读写分离在ShardingJDBC里怎么配以及分片和读写分离叠加时数据源命名、负载均衡算法怎么处理。这个组合场景也是网上资料最容易模糊的地方。先用好这篇的yaml配置把一条SQL从发起到路由到物理库的完整链路跑通后面所有高可用策略都有基础了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑