目录


1 Sharding-JDBC 简介


摘自官网介绍:https://shardingsphere.apache.org/document/legacy/3.x/document/cn/overview/


定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。

基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。

支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。

Myqsql使用Sharding-JDBC分表分库和读写分离_分表分库

现在的 ShardingSphere 不单单是指某个框架而是一个生态圈,这个生态圈 、​​Sharding-Proxy​​​ 和 ​​Sharding-Sidecar​​ 这三款开源的分布式数据库中间件解决方案所构成。

的前身就是 ,所以它是整个框架中最为经典、成熟的组件,我们先从 框架入手学习分库分表。

2 核心概念

在开始分库分表具体实战之前,我们有必要先了解分库分表的一些核心概念。

​逻辑表​

水平拆分的数据表的总称。例:订单数据表根据主键尾数拆分为10张表,分别是 t_order_0 、 t_order_1 到

t_order_9 ,他们的逻辑表名为 t_order 。

​真实表​

在分片的数据库中真实存在的物理表。即上个示例中的 t_order_0 到 t_order_9 。

​数据节点​

数据分片的最小物理单元。由数据源名称和数据表组成,例: ds_0.t_order_0 。

​绑定表​

指分片规则一致的主表和子表。例如: t_order 表和 t_order_item 表,均按照 order_id 分片,绑定表之间的分区键完全相同,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果SQL为:

SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10,
11);

在不配置绑定表关系时,假设分片键 order_id 将数值10路由至第0片,将数值11路由至第1片,那么路由后的SQL

应该为4条,它们呈现为笛卡尔积:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in
(10, 11);
SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in
(10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in
(10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in
(10, 11);

在配置绑定表关系后,路由的SQL应该为2条:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in
(10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in
(10, 11);

​广播表​

指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与

海量数据的表进行关联查询的场景,例如:字典表。

​分片键​

用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,Sharding-Jdbc也支持根据多个字段进行分片。

​分片算法​

通过分片算法将数据分片,支持通过 = 、 BETWEEN 和 IN 分片。分片算法需要应用方开发者自行实现,可实现的灵

活度非常高。包括:精确分片算法 、范围分片算法 ,复合分片算法 等。例如:where order_id = ? 将采用精确分片算法,where order_id in (?,?,?)将采用精确分片算法,where order_id BETWEEN ? and ? 将采用范围分片算法,复合分片算法用于分片键有多个复杂情况。

​分片策略​

包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。内置的分片策略大致可分为尾数取模、哈希、范围、标签、时间等。由用户方配置的分片策略则更加灵活,常用的使用行表达式配置分片策略,它采用Groovy表达式表示,如: t_user_$ ->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为 t_user_0 到 t_user_7 。

​自增主键生成策略​

通过在客户端生成自增主键替换以数据库原生自增主键的方式,做到分布式主键无重复。

3 Sharding-JDBC和JDBC的关系

从名字上不难看出, 和 ​​JDBC​​​有很大关系,我们知道 JDBC 是一种 ​​Java​​ 语言访问关系型数据库的规范,其设计初衷就是要提供一套用于各种数据库的统一标准,不同厂家共同遵守这套标准,并提供各自的实现方案供应用程序调用。

但其实对于开发人员而言,我们只关心如何调用 JDBC API 来访问数据库,只要正确使用 ​​DataSource​​​、​​Connection​​​、​​Statement​​​ 、​​ResultSet​​ 等 API 接口,直接操作数据库即可。所以如果想在 JDBC 层面实现数据分片就必须对现有的 API 进行功能拓展,而 Sharding-JDBC 正是基于这种思想,重写了 JDBC 规范并完全兼容了 JDBC 规范。

对原有的 、 等接口扩展成 ​​ShardingDataSource​​​、​​ShardingConnection​​,而对外暴露的分片操作接口与 JDBC 规范中所提供的接口完全一致,只要你熟悉 JDBC 就可以轻松应用 Sharding-JDBC 来实现分库分表。

因此它适用于任何基于 的 ​​ORM​​​ 框架,如:​​JPA​​​, ​​Hibernate​​​,​​Mybatis​​​,​​Spring JDBC Template​​​ 或直接使用的 JDBC。完美兼容任何第三方的数据库连接池,如:​​DBCP​​​, ​​C3P0​​​, ​​BoneCP​​​,​​Druid​​​, ​​HikariCP​​ 等,几乎对主流关系型数据库都支持。

4 Sharding-JDBC 执行原理

4.1 SQL解析

当Sharding-JDBC接受到一条SQL语句时,会陆续执行 SQL解析 => 查询优化 => SQL路由 => SQL改写 => SQL执行 =>结果归并 ,最终返回执行结果。

Myqsql使用Sharding-JDBC分表分库和读写分离_sql_02

SQL解析过程分为词法解析和语法解析。 词法解析器用于将SQL拆解为不可再分的原子符号,称为Token。并根据不同数据库方言所提供的字典,将其归类为关键字,表达式,字面量和操作符。 再使用语法解析器将SQL转换为抽象语法树。

例如,以下SQL:

​SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18​

解析之后的为抽象语法树见下图:

Myqsql使用Sharding-JDBC分表分库和读写分离_分表分库_03

为了便于理解,抽象语法树中的关键字的Token用绿色表示,变量的Token用红色表示,灰色表示需要进一步拆分。

最后,通过对抽象语法树的遍历去提炼分片所需的上下文,并标记有可能需要SQL改写(后边介绍)的位置。 供分片使用的解析上下文包含查询选择项(Select Items)、表信息(Table)、分片条件(Sharding Condition)、自增主键信息(Auto increment Primary Key)、排序信息(Order By)、分组信息(Group By)以及分页信息(Limit、Rownum、Top)。

4.2 SQL路由

SQL路由就是把针对逻辑表的数据操作映射到对数据结点操作的过程。

根据解析上下文匹配数据库和表的分片策略,并生成路由路径。 对于携带分片键的SQL,根据分片键操作符不同可以划分为单片路由(分片键的操作符是等号)、多片路由(分片键的操作符是IN)和范围路由(分片键的操作符是BETWEEN),不携带分片键的SQL则采用广播路由。根据分片键进行路由的场景可分为直接路由、标准路由、笛卡尔路由等。标准路由标准路由是Sharding-Jdbc最为推荐使用的分片方式,它的适用范围是不包含关联查询或仅包含绑定表之间关联查询的SQL。 当分片运算符是等于号时,路由结果将落入单库(表),当分片运算符是BETWEEN或IN时,则路由结果不一定落入唯一的库(表),因此一条逻辑SQL最终可能被拆分为多条用于执行的真实SQL。 举例说明,如果按照 order_id 的奇数和偶数进行数据分片,一个单表查询的SQL如下:

​SELECT * FROM t_order WHERE order_id IN (1, 2);​

那么路由的结果应为:

SELECT * FROM t_order_0 WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 WHERE order_id IN (1, 2);

绑定表的关联查询与单表查询复杂度和性能相当。举例说明,如果一个包含绑定表的关联查询的 SQL如下:

​SELECT * FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE order_id IN (1, 2);​

那么路由的结果应为:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id  WHERE order_id IN (1,
2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1,
2);

可以看到,SQL拆分的数目与单表是一致的。

笛卡尔路由笛卡尔路由是最复杂的情况,它无法根据绑定表的关系定位分片规则,因此非绑定表之间的关联查询要拆解为笛卡尔积组合执行。 如果上个示例中的SQL并未配置绑定表关系,那么路由的结果应为:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id  WHERE order_id IN (1,
2);
SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1,
2);
SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE order_id IN (1,
2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1,
2);

笛卡尔路由查询性能较低,需谨慎使用。

全库表路由对于不携带分片键的SQL,则采取广播路由的方式。根据SQL类型又可以划分为全库表路由、全库路由、全实例路由、单播路由和阻断路由这5种类型。其中全库表路由用于处理对数据库中与其逻辑表相关的所有真实表的操作,主要包括不带分片键的DQL(数据查询)和DML(数据操纵),以及DDL(数据定义)等。例如:

​SELECT * FROM t_order WHERE good_prority IN (1, 10);​

则会遍历所有数据库中的所有表,逐一匹配逻辑表和真实表名,能够匹配得上则执行。路由后成为

SELECT * FROM t_order_0 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_1 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_2 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_3 WHERE good_prority IN (1, 10);

4.3 SQL改写

工程师面向逻辑表书写的SQL,并不能够直接在真实的数据库中执行,SQL改写用于将逻辑SQL改写为在真实数据库中可以正确执行的SQL。

如一个简单的例子,若逻辑SQL为:

​SELECT order_id FROM t_order WHERE order_id=1;​

假设该SQL配置分片键order_id,并且order_id=1的情况,将路由至分片表1。那么改写之后的SQL应该为:

​SELECT order_id FROM t_order_1 WHERE order_id=1;​

再比如,Sharding-JDBC需要在结果归并时获取相应数据,但该数据并未能通过查询的SQL返回。 这种情况主要是针对GROUP BY和ORDER BY。结果归并时,需要根据 GROUP BY 和 ORDER BY 的字段项进行分组和排序,但如果原始SQL的选择项中若并未包含分组项或排序项,则需要对原始SQL进行改写。 先看一下原始SQL中带有结果归并所需信息的场景:

​SELECT order_id, user_id FROM t_order ORDER BY user_id;​​由于使用user_id进行排序,在结果归并中需要能够获取到user_id的数据,而上面的SQL是能够获取到user_id数据的,因此无需补列。

如果选择项中不包含结果归并时所需的列,则需要进行补列,如以下SQL:

​SELECT order_id FROM t_order ORDER BY user_id;​

由于原始SQL中并不包含需要在结果归并中需要获取的user_id,因此需要对SQL进行补列改写。补列之后的SQL

是:

​SELECT order_id, user_id AS ORDER_BY_DERIVED_0 FROM t_order ORDER BY user_id;​

4.4.SQL执行

Sharding-JDBC采用一套自动化的执行引擎,负责将路由和改写完成之后的真实SQL安全且高效发送到底层数据源执行。 它不是简单地将SQL通过JDBC直接发送至数据源执行;也并非直接将执行请求放入线程池去并发执行。它更关注平衡数据源连接创建以及内存占用所产生的消耗,以及最大限度地合理利用并发等问题。 执行引擎的目标是自动化的平衡资源控制与执行效率,他能在以下两种模式自适应切换:内存限制模式使用此模式的前提是, Sharding-JDBC对一次操作所耗费的数据库连接数量不做限制。 如果实际执行的SQL需要对某数据库实例中的200张表做操作,则对每张表创建一个新的数据库连接,并通过多线程的方式并发处理,以达成执行效率最大化。连接限制模式使用此模式的前提是,Sharding-JDBC严格控制对一次操作所耗费的数据库连接数量。 如果实际执行的SQL需要对某数据库实例中的200张表做操作,那么只会创建唯一的数据库连接,并对其200张表串行处理。 如果一次操作中的分片散落在不同的数据库,仍然采用多线程处理对不同库的操作,但每个库的每次操作仍然只创建一个唯一的数据库连接。内存限制模式适用于OLAP操作,可以通过放宽对数据库连接的限制提升系统吞吐量; 连接限制模式适用于OLTP操作,OLTP通常带有分片键,会路由到单一的分片,因此严格控制数据库连接,以保证在线系统数据库资源能够被更多的应用所使用,是明智的选择。

4.5.结果归并

将从各个数据节点获取的多数据结果集,组合成为一个结果集并正确的返回至请求客户端,称为结果归并。

Sharding-JDBC支持的结果归并从功能上可分为遍历、排序、分组、分页和聚合5种类型,它们是组合而非互斥的关系。

归并引擎的整体结构划分如下图。

Myqsql使用Sharding-JDBC分表分库和读写分离_spring_04

结果归并从结构划分可分为流式归并、内存归并和装饰者归并。流式归并和内存归并是互斥的,装饰者归并可以在流式归并和内存归并之上做进一步的处理。内存归并很容易理解,他是将所有分片结果集的数据都遍历并存储在内存中,再通过统一的分组、排序以及聚合等计算之后,再将其封装成为逐条访问的数据结果集返回。

流式归并 是指每一次从数据库结果集中获取到的数据,都能够通过游标逐条获取的方式返回正确的单条数据,它与数据库原生的返回结果集的方式最为契合。下边举例说明排序归并的过程,如下图是一个通过分数进行排序的示例图,它采用流式归并方式。 图中展示了3张表返回的数据结果集,每个数据结果集已经根据分数排序完毕,但是3个数据结果集之间是无序的。 将3个数据结果集的当前游标指向的数据值进行排序,并放入优先级队列,t_score_0的第一个数据值最大,t_score_2的第一个

数据值次之,t_score_1的第一个数据值最小,因此优先级队列根据t_score_0,t_score_2和t_score_1的方式排序队列。

Myqsql使用Sharding-JDBC分表分库和读写分离_spring_05

下图则展现了进行next调用的时候,排序归并是如何进行的。 通过图中我们可以看到,当进行第一次next调用时,排在队列首位的t_score_0将会被弹出队列,并且将当前游标指向的数据值(也就是100)返回至查询客户端,并且将游标下移一位之后,重新放入优先级队列。 而优先级队列也会根据t_score_0的当前数据结果集指向游标的数据值(这里是90)进行排序,根据当前数值,t_score_0排列在队列的最后一位。 之前队列中排名第二的t_score_2的数据结果集则自动排在了队列首位。

在进行第二次next时,只需要将目前排列在队列首位的t_score_2弹出队列,并且将其数据结果集游标指向的值返回至客户端,并下移游标,继续加入队列排队,以此类推。 当一个结果集中已经没有数据了,则无需再次加入队列。

Myqsql使用Sharding-JDBC分表分库和读写分离_sql_06

可以看到,对于每个数据结果集中的数据有序,而多数据结果集整体无序的情况下,Sharding-JDBC无需将所有的数据都加载至内存即可排序。 它使用的是流式归并的方式,每次next仅获取唯一正确的一条数据,极大的节省了内存的消耗。装饰者归并是对所有的结果集归并进行统一的功能增强,比如归并时需要聚合SUM前,在进行聚合计算前,都会通过内存归并或流式归并查询出结果集。因此,聚合归并是在之前介绍的归并类型之上追加的归并能力,即装饰者模式。

4.6 总结

通过以上内容介绍,相信大家已经了解到Sharding-JDBC基础概念、核心功能以及执行原理。

基础概念:逻辑表,真实表,数据节点,绑定表,广播表,分片键,分片算法,分片策略,主键生成策略核心功能:数据分片,读写分离

执行流程: SQL 解析 => 查询优化 => SQL路由 => SQL改写 => SQL执行 => 结果归并

接下来我们将通过一个个demo,来演示Sharding-JDBC实际使用方法。

5 快速入门分表分库

下面我们结合 ​​Springboot​​​ + ​​mybatisplus​​ 快速搭建一个分库分表案例。

1、准备工作

先做准备工作,创建两个数据库 ​​ds-0​​​、​​ds-1​​​,两个库中分别建表 ​​t_order_0​​​、​​t_order_1​​​、​​t_order_2​​​、​​t_order_item_0​​​、​​t_order_item_1​​​、​​t_order_item_2​​​,​​t_config​​,方便后边验证广播表、绑定表的场景。

Myqsql使用Sharding-JDBC分表分库和读写分离_分表分库_07

表结构如下:

​t_order_0​​ 订单表

CREATE TABLE `t_order_0` (
`order_id` bigint(200) NOT NULL,
`order_no` varchar(100) DEFAULT NULL,
`create_name` varchar(50) DEFAULT NULL,
`price` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

​t_order_0​​​ 与 ​​t_order_item_0​​ 互为关联表

CREATE TABLE `t_order_item_0` (
`item_id` bigint(100) NOT NULL,
`order_no` varchar(200) NOT NULL,
`item_name` varchar(50) DEFAULT NULL,
`price` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

广播表 ​​t_config​

`id` bigint(30) NOT NULL,
`remark` varchar(50) CHARACTER SET utf8 DEFAULT NULL,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_modify_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

​ShardingSphere​​ 提供了4种分片配置方式:

  • Java 代码配置
  • Yaml 、properties 配置
  • Spring 命名空间配置
  • Spring Boot配置

为让代码看上去更简洁和直观,后边统一使用 ​​properties​​​ 配置的方式,引入 ​​shardingsphere​​​ 对应的 ​​sharding-jdbc-spring-boot-starter​​​ 和 ​​sharding-core-common​​ 包,版本统一用的 4.0.0-RC1。

2、分片配置

<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-core-common</artifactId>
<version>4.0.0-RC1</version>
</dependency>

准备工作做完( mybatis 搭建就不赘述了),接下来我们逐一解读分片配置信息。

我们首先定义两个数据源 ​​ds-0​​​、​​ds-1​​,并分别加上数据源的基础信息。

# 定义两个全局数据源spring.shardingsphere.datasource.names=ds-0,ds-1

# 配置数据源
ds-0spring.shardingsphere.datasource.
ds-0.type=com.alibaba.druid.pool.DruidDataSourcespring.shardingsphere.datasource.
ds-0.driverClassName=com.mysql.jdbc.Driverspring.shardingsphere.datasource.
ds-0.url=jdbc:mysql://127.0.0.1:3306/ds-0?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMTspring.shardingsphere.datasource.ds-0.username=rootspring.shardingsphere.datasource.ds-0.password=root

# 配置数据源
ds-1spring.shardingsphere.datasource.
ds-1.type=com.alibaba.druid.pool.DruidDataSourcespring.shardingsphere.datasource.
ds-1.driverClassName=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds-1.url=jdbc:mysql://127.0.0.1:3306/ds-1?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMTspring.shardingsphere.datasource.ds-1.username=rootspring.shardingsphere.datasource.ds-1.password=root

配置完数据源接下来为表添加分库和分表策略,使用 ​​sharding-jdbc​​ 做分库分表需要我们为每一个表单独设置分片规则。

# 配置分片表 t_order
# 指定真实数据节点
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds-$->{0..1}.t_order_$->{0..2}

​actual-data-nodes​​​ 属性指定分片的真实数据节点,​​$​​是一个占位符,{0…1}表示实际拆分的数据库表数量。

​ds-$->{0..1}.t_order_$->{0..2}​​ 表达式相当于 6个数据节点

  • ds-0.t_order_0
  • ds-0.t_order_1
  • ds-0.t_order_2
  • ds-1.t_order_0
  • ds-1.t_order_1
  • ds-1.t_order_2
### 分库策略

# 分库分片健
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column=order_id
# 分库分片算法
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression=ds-$->{order_id % 2}

为表设置分库策略,上边讲了 ​​sharding-jdbc​​ 它提供了四种分片策略,为快速搭建我们先以最简单的行内表达式分片策略来实现,在下一篇会介绍四种分片策略的详细用法和使用场景。

​database-strategy.inline.sharding-column​​​ 属性中 ​​database-strategy​​​ 为分库策略,​​inline​​​ 为具体的分片策略,​​sharding-column​​ 代表分片健。

​database-strategy.inline.algorithm-expression​​​ 是当前策略下具体的分片算法,​​ds-$->{order_id % 2}​​​ 表达式意思是 对 ​​order_id​​字段进行取模分库,2 代表分片库的个数,不同的策略对应不同的算法,这里也可以是我们自定义的分片算法类。

# 分表策略# 分表分片健spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id# 分表算法spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{order_id % 3}# 自增主键字段spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id# 自增主键ID 生成方案spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE

分表策略 和 分库策略 的配置比较相似,不同的是分表可以通过 ​​key-generator.column​​​ 和 ​​key-generator.type​​​ 设置自增主键以及指定自增主键的生成方案,目前内置了​​SNOWFLAKE​​​ 和 ​​UUID​​ 两种方式,还能自定义的主键生成算法类,后续会详细的讲解。

# 绑定表关系spring.shardingsphere.sharding.binding-tables= t_order,t_order_item

必须按相同分片健进行分片的表才能互为成绑定表,在联合查询时就能避免出现笛卡尔积查询。

# 配置广播表spring.shardingsphere.sharding.broadcast-tables=t_config

广播表,开启 SQL解析日志,能清晰的看到 SQL分片解析的过程

# 是否开启 SQL解析日志
spring.shardingsphere.props.sql.show=true

3、验证分片

分片配置完以后我们无需在修改业务代码了,直接执行业务逻辑的增、删、改、查即可,接下来验证一下分片的效果。

我们同时向 ​​t_order​​​、​​t_order_item​​​ 表插入 5条订单记录,并不给定主键 ​​order_id​​​ ,​​item_id​​字段值。

public String insertOrder() {
for (int i = 0; i < 4; i++) {
TOrder order = new TOrder();
order.setOrderNo("A000" + i);
order.setCreateName("订单 " + i);
order.setPrice(new BigDecimal("" + i));
orderRepository.insert(order);
TOrderItem orderItem = new TOrderItem();
orderItem.setOrderId(order.getOrderId());
orderItem.setOrderNo("A000" + i);
orderItem.setItemName("服务项目" + i);
orderItem.setPrice(new BigDecimal("" + i));
orderItemRepository.insert(orderItem);
}
return "success";
}

看到订单记录被成功分散到了不同的库表中, ​​order_id​​ 字段也自动生成了主键ID,基础的分片功能就完成了。

Myqsql使用Sharding-JDBC分表分库和读写分离_读写分离_08

那向广播表 ​​t_config​​ 中插入一条数据会是什么效果呢?

public String config() {
TConfig tConfig = new TConfig();
tConfig.setRemark("我是广播表");
tConfig.setCreateTime(new Date());
tConfig.setLastModifyTime(new Date());
configRepository.insert(tConfig);
return "success";
}

发现所有库中 ​​t_config​​ 表都执行了这条SQL,广播表和 MQ广播订阅的模式很相似,所有订阅的客户端都会收到同一条消息。

Myqsql使用Sharding-JDBC分表分库和读写分离_spring_09

简单SQL操作验证没问通,接下来在试试复杂一点的联合查询,前边我们已经把 ​​t_order​​​、​​t_order_item​​ 表设为绑定表,直接联表查询执行一下。

Myqsql使用Sharding-JDBC分表分库和读写分离_分表分库_10

通过控制台日志发现,逻辑表SQL 经过解析以后,只对 ​​t_order_0​​​ 和 ​​t_order_item_0​​ 表进行了关联产生一条SQL。

Myqsql使用Sharding-JDBC分表分库和读写分离_spring_11

那如果不互为绑定表又会是什么情况呢?去掉 ​​spring.shardingsphere.sharding.binding-tables​​试一下。

发现控制台解析出了 3条真实表SQL,而去掉 ​​order_id​​ 作为查询条件再次执行后,结果解析出了 9条SQL,进行了笛卡尔积查询。所以相比之下绑定表的优点就不言而喻了。

Myqsql使用Sharding-JDBC分表分库和读写分离_分表分库_12

6 快速入门读写分离

(1)在Sharding-JDBC规则中修改

# 增加数据源s0,使用上面主从同步配置的从库。
spring.shardingsphere.datasource.names = m0,m1,m2,s0
...
spring.shardingsphere.datasource.s0.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s0.driver‐class‐name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s0.url = jdbc:mysql://localhost:3307/user_db?useUnicode=true
spring.shardingsphere.datasource.s0.username = root
spring.shardingsphere.datasource.s0.password = root
....
# 主库从库逻辑数据源定义 ds0为user_db
spring.shardingsphere.sharding.master‐slave‐rules.ds0.master‐data‐source‐name=m0
spring.shardingsphere.sharding.master‐slave‐rules.ds0.slave‐data‐source‐names=s0
# t_user分表策略,固定分配至ds0的t_user真实表
spring.shardingsphere.sharding.tables.t_user.actual‐data‐nodes = ds0.t_user
....

(2)测试

执行testInsertUser单元测试:

Myqsql使用Sharding-JDBC分表分库和读写分离_分表分库_13

通过日志可以看出,所有写操作落入m0数据源。

执行testSelectUserbyIds单元测试:

Myqsql使用Sharding-JDBC分表分库和读写分离_spring_14

通过日志可以看出,所有读操作落入s0数据源,达到目标。