- 前言
- 需求说明
- 一、环境搭建?
- 1 环境说明
- 2 创建数据库
- 3.引入maven依赖
- 二.编写程序
- 1 分片规则配置
- 2.数据操作
- 3.测试
- 4.流程分析
- 5.其他集成方式
- 5.1 Spring Boot Yaml 配置
- 三.Sharding-JDBC执行原理
- 1. 基本概念
- 2.SQL解析
- 3.SQL路由
- 4.SQL改写
- 5.SQL执行
- 6 结果归并
- 7 总结
前言 需求说明
使用Sharding-JDBC完成对订单表的水平分表 ,通过快速入门程序的开发 ,快速体验Sharding-JDBC的使用 方法。
人工创建两张表 ,t_order_1和t_order_2 ,这两张表是订单表拆分后的表 ,通过Sharding-Jdbc向订单表插入数据 , 按照一定的分片规则 ,主键为偶数的进入t_order_1 ,另一部分数据进入t_order_2 ,通过Sharding-Jdbc 查询数 据 ,根据 SQL语句的内容从t_order_1或t_order_2查询数据。
一、环境搭建? 1 环境说明
- 操作系统 :Win10
- 数据库:MySQL-5.7.25
- JDK:64位 jdk1.8.0_201
- 应用框架:spring-boot-2.1.3.RELEASE,Mybatis3.5.0
- Sharding-JDBC:sharding-jdbc-spring-boot-starter-4.0.0-RC1
版本没有强制要求
创建订单库order_db
CREATE DATAbase `order_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
在order_db中创建t_order_1、t_order_2表
DROp TABLE IF EXISTS `t_order_1`; CREATE TABLE `t_order_1` ( `order_id` bigint(20) NOT NULL COMMENT '订单id', `price` decimal(10, 2) NOT NULL COMMENT '订单价格', `user_id` bigint(20) NOT NULL COMMENT '下单用户id', `status` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单状态', PRIMARY KEY USING BTREE (`order_id`) ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE utf8_general_ci ROW_FORMAT = Dynamic; DROP TABLE IF EXISTS `t_order_2`; CREATE TABLE `t_order_2` ( `order_id` bigint(20) NOT NULL COMMENT '订单id', `price` decimal(10, 2) NOT NULL COMMENT '订单价格', `user_id` bigint(20) NOT NULL COMMENT '下单用户id', `status` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单状态', PRIMARY KEY USING BTREE (`order_id`) ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE utf8_general_ci ROW_FORMAT = Dynamic;3.引入maven依赖
引入 sharding-jdbc和SpringBoot整合的Jar包 :
io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2 org.projectlombok lombok 1.18.0 javax.interceptor javax.interceptor-api 1.2 mysql mysql-connector-java 5.1.47 org.mybatis.spring.boot mybatis-spring-boot-starter 2.0.0 com.alibaba druid-spring-boot-starter 1.1.16 org.apache.shardingsphere sharding-jdbc-spring-boot-starter 4.0.0-RC1 com.baomidou mybatis-plus-boot-starter 3.1.0 com.baomidou mybatis-plus-generator 3.1.0 org.mybatis mybatis-typehandlers-jsr310 1.0.2 org.springframework.boot spring-boot-starter-test junit junit 4.13.2 test maven-releases maven-releases https://maven.aliyun.com/repository/public maven-snapshots maven-snapshots https://maven.aliyun.com/repository/snapshots ${project.name} src/main/resources true ***.xml org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 1.8 1.8 maven-resources-plugin utf-8 true
具体spring boot相关依赖及配置请参考资料中dbsharding/sharding-jdbc-simple工程 ,本指引只说明与Sharding- JDBC相关的内容。
二.编写程序 1 分片规则配置分片规则配置是sharding-jdbc进行对分库分表操作的重要依据 ,配置内容包括 :数据源、 主键生成策略、 分片策 略等。
在application.properties中配置
server.port=56081
spring.application.name = sharding‐jdbc‐simple‐demo
server.servlet.context‐path = /sharding‐jdbc‐simple‐demo
spring.http.encoding.enabled = true
spring.http.encoding.charset = UTF‐8
spring.http.encoding.force = true
spring.main.allow‐bean‐definition‐overriding = true
mybatis.configuration.map‐underscore‐to‐camel‐case = true
# 以下是分片规则配置
# 定义数据源
spring.shardingsphere.datasource.names = m1
spring.shardingsphere.datasource.m1.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver‐class‐name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url = jdbc:mysql://localhost:3306/order_dbuseUnicode=true
spring.shardingsphere.datasource.m1.username = root
spring.shardingsphere.datasource.m1.password = root
# 指定t_order表的数据分布情况,配置数据节点
spring.shardingsphere.sharding.tables.t_order.actual‐data‐nodes = m1.t_order_$‐>{1..2}
# 指定t_order表的主键生成策略为SNOWFLAKE
spring.shardingsphere.sharding.tables.t_order.key‐generator.column=order_id
spring.shardingsphere.sharding.tables.t_order.key‐generator.type=SNOWFLAKE
# 指定t_order表的分片策略,分片策略包括分片键和分片算法
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 % 2 + 1}
# 打开sql输出日志
spring.shardingsphere.props.sql.show = true
swagger.enable = true
logging.level.root = info
logging.level.org.springframework.web = info
logging.level.com.itheima.dbsharding = debug
logging.level.druid.sql = debug
1.首先定义数据源m1,并对m1进行实际的参数配置。
2.指定t_order表的数据分布情况,他分布在m1.t_order_1,m1.t_order_2
3.指定t_order表的主键生成策略为SNOWFLAKE,SNOWFLAKE是一种分布式自增算法,保证id全局唯一
4.定义t_order分片策略,order_id为偶数的数据落在t_order_1,为奇数的落在t_order_2,分表策略的表达式为
t_order_$->{order_id % 2 + 1}
2.数据操作@Mapper
@Component
public interface OrderDao {
@Insert("insert into t_order(price,user_id,status) value(#{price},#{userId},#{status})")
int insertOrder(@Param("price") BigDecimal price, @Param("userId")Long userId, @Param("status") String status);
@Select({""})
List
3.测试
编写单元测试 :
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {ShardingJdbcSimpleDemoBootstrap.class})
public class OrderDaoTest {
@Autowired
private OrderDao orderDao;
@Test
public void testInsertOrder() {
for (int i = 0; i < 10; i++) {
orderDao.insertOrder(new BigDecimal((i + 1) * 5), 1L, "WAIT_PAY");
}
}
@Test
public void testSelectOrderbyIds() {
List ids = new ArrayList<>();
ids.add(373771636085620736L);
ids.add(373771635804602369L);
List
执行testInsertOrder:
通过日志可以发现order_id为奇数的被插入到t_order_2表,为偶数的被插入到t_order_1表,达到预期目标。
执行testSelectOrderbyIds:
通过日志可以发现,根据传入order_id的奇偶不同,sharding-jdbc分别去不同的表检索数据,达到预期目标。
通过日志分析 ,Sharding-JDBC在拿到用户要执行的sql之后干了哪些事儿 :
( 1 )解析sql ,获取片键值 ,在本例中是order_id
( 2 )Sharding-JDBC通过规则配置 t_order_$->{order_id % 2 + 1} ,知道了当order_id为偶数时 ,应该往 t_order_1表插数据 ,为奇数时 ,往t_order_2插数据。
( 3 )于是Sharding-JDBC根据order_id的值改写sql语句 ,改写后的SQL语句是真实所要执行的SQL语句。
( 4 )执行改写后的真实sql语句
( 5 )将所有真正执行sql的结果进行汇总合并 ,返回。
5.其他集成方式Sharding-JDBC不仅可以与spring boot良好集成 ,它还支持其他配置方式 。
5.1 Spring Boot Yaml 配置定义application.yml ,内容如下 :
server:
port: 56081
servlet:
context-path: /sharding-jdbc-simple-demo
spring:
application:
name: sharding-jdbc-simple-demo
http:
encoding:
enabled: true
charset: utf-8
force: true
main:
allow-bean-definition-overriding: true
shardingsphere:
datasource:
names: m1
m1:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/order_db?useUnicode=true
username: root
password: mysql
sharding:
tables:
t_order:
actualDataNodes: m1.t_order_$->{1..2}
tableStrategy:
inline:
shardingColumn: order_id
algorithmexpression: t_order_$->{order_id % 2 + 1}
keyGenerator:
type: SNOWFLAKE
column: order_id
props:
sql:
show: true
mybatis:
configuration:
map-underscore-to-camel-case: true
swagger:
enable: true
logging:
level:
root: info
org.springframework.web: info
com.itheima.dbsharding: debug
druid.sql: debug
三.Sharding-JDBC执行原理
1. 基本概念
在了解Sharding-JDBC的执行原理前 ,需要了解以下概念 :
逻辑表
水平拆分的数据表的总称。 例 :订单数据表根据主键尾数拆分为10张表 ,分别是 t_order_0、t_order_1到t_order_9 ,他们的逻辑表名为t_order。
真实表
数据分片的最小物理单元。由数据源名称和数据表组成,例: 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 。
自增主键生成策略
通过在客户端生成自增主键替换以数据库原生自增主键的方式 ,做到分布式主键无重复。
2.SQL解析当Sharding-JDBC接受到一条SQL语句时,会陆续执行SQL解析 => 查询优化 => SQL路由 => SQL改写 => SQL执行 => 结果归并,最终返回执行结果。
SQL解析过程分为词法解析和语法解析。 词法解析器用于将SQL拆解为不可再分的原子符号,称为Token。并根据
不同数据库方言所提供的字典,将其归类为关键字,表达式,字面量和操作符。 再使用语法解析器将SQL转换为抽 象语法树。
3.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.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;5.SQL执行
Sharding-JDBC采用一套自动化的执行引擎 ,负责将路由和改写完成之后的真实SQL安全且高效发送到底层数据源 执行。 它不是简单地将SQL通过JDBC直接发送至数据源执行 ;也并非直接将执行请求放入线程池去并发执行。 它更关注平衡数据源连接创建以及内存占用所产生的消耗 ,以及最大限度地合理利用并发等问题。 执行引擎的目标是 自动化的平衡资源控制与执行效率 ,他能在以下两种模式自适应切换 :
内存限制模式
使用此模式的前提是 ,Sharding-JDBC对一次操作所耗费的数据库连接数量不做限制。 如果实际执行的SQL需要对 某数据库实例中的200张表做操作 ,则对每张表创建一个新的数据库连接 ,并通过多线程的方式并发处理 ,以达成 执行效率最大化。
连接限制模式
使用此模式的前提是 ,Sharding-JDBC严格控制对一次操作所耗费的数据库连接数量。 如果实际执行的SQL需要对 某数据库实例中的200张表做操作 ,那么只会创建唯一的数据库连接 ,并对其200张表串行处理。 如果一次操作中 的分片散落在不同的数据库 ,仍然采用多线程处理对不同库的操作 ,但每个库的每次操作仍然只创建一个唯一的数 据库连接。
内存限制模式适用于OLAP操作 ,可以通过放宽对数据库连接的限制提升系统吞吐量 ; 连接限制模式适用于OLTP操 作 ,OLTP通常带有分片键 ,会路由到单一的分片 ,因此严格控制数据库连接 ,以保证在线系统数据库资源能够被 更多的应用所使用 ,是明智的选择。
6 结果归并将从各个数据节点获取的多数据结果集,组合成为一个结果集并正确的返回至请求客户端,称为结果归并。
Sharding-JDBC支持的结果归并从功能上可分为遍历、排序、分组、分页和聚合5种类型,它们是组合而非互斥的
关系。
归并引擎的整体结构划分如下图。
结果归并从结构划分可分为流式归并、内存归并和装饰者归并。流式归并和内存归并是互斥的,装饰者归并可以在
流式归并和内存归并之上做进一步的处理。
内存归并很容易理解,他是将所有分片结果集的数据都遍历并存储在内存中,再通过统一的分组、排序以及聚合等
计算之后,再将其封装成为逐条访问的数据结果集返回。
流式归并是指每一次从数据库结果集中获取到的数据,都能够通过游标逐条获取的方式返回正确的单条数据,它与数据库原生的返回结果集的方式最为契合。
7 总结通过以上内容介绍 ,相信大家已经了解到Sharding-JDBC基础概念、 核心功能以及执行原理。
基础概念 :逻辑表 ,真实表 ,数据节点 ,绑定表 ,广播表 ,分片键 ,分片算法 ,分片策略 ,主键生成策略
核心功能 :数据分片 ,读写分离
执行流程: SQL解析 => 查询优化 => SQL路由 => SQL改写 => SQL执行 => 结果归并
下一章我们将通过一个个demo ,来演示Sharding-JDBC实际使用方法。



