分库分表 - 6:Sharding-JDBC执行原理


目录:分库分表 Sharding-JDBC从入门到精通

主题 连接地址
准备1: 在window安装虚拟机集群 分布式 虚拟机 linux 环境制做 GO
准备2:在虚拟机的各个节点有 mysql centos mysql 笔记(内含vagrant mysql 镜像)GO
分库分表 -Sharding-JDBC- 从入门到精通 1 Sharding-JDBC 分库、分表(入门实战) GO
分库分表 -Sharding-JDBC- 从入门到精通 2 Sharding-JDBC 基础知识 GO
分库分表 Sharding-JDBC 从入门到精通之 3 自定义主键、分布式雪花主键,原理与实战 GO
分库分表 -Sharding-JDBC- 从入门到精通 4 MYSQL集群主从复制,原理与实战 GO
分库分表 Sharding-JDBC 从入门到精通之 5 读写分离 实战 GO
分库分表 Sharding-JDBC 从入门到精通之 6 Sharding-JDBC执行原理 GO
分库分表 Sharding-JDBC 从入门到精通之源码 git仓库地址GO

Sharding-JDBC执行原理

0、本文目录

  1. 基本概念
  2. SQL解析
  3. SQL路由
  4. SQL改写
  5. SQL执行
  6. 结果归并

一、 基本概念

在了解Sharding-JDBC的执行原理前,须要了解如下概念:
逻辑表
水平拆分的数据表的总称。例:订单数据表根据主键尾数拆分为10张表,分别是 t_order_0 、 t_order_1 到t_order_9 ,他们的逻辑表名为 t_order 。
真实表
在分片的数据库中真实存在的物理表。即上个示例中的 t_order_0 到 t_order_9 。linux

数据节点
数据分片的最小物理单元。由数据源名称和数据表组成,例: ds_0.t_order_0 。
绑定表
指分片规则一致的主表和子表。例如: t_order 表和 t_order_item 表,均按照 order_id 分片,绑定表之间的分区
键彻底相同,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大
大提高。举例说明,若是SQL为:git

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条:redis

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中若是无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,ShardingJdbc也支持根据多个字段进行分片。spring

分片算法

经过分片算法将数据分片,支持经过 = 、 BETWEEN 和 IN 分片。分片算法须要应用方开发者自行实现,可实现的灵
活度很是高。包括:精确分片算法 、范围分片算法 ,复合分片算法 等。例如:where order_id = ? 将采用精确分
片算法,where order_id in (?,?,?)将采用精确分片算法,where order_id BETWEEN ? and ? 将采用范围分片算
法,复合分片算法用于分片键有多个复杂状况。sql

分片策略

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

自增主键生成策略

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

二、SQL解析

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

img

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

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

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

img

为了便于理解,抽象语法树中的关键字的Token用绿色表示,变量的Token用红色表示,灰色表示须要进一步拆分。
最后,经过对抽象语法树的遍历去提炼分片所需的上下文,并标记有可能须要SQL改写(后边介绍)的位置。 供分片
使用的解析上下文包含查询选择项(Select Items)、表信息(Table)、分片条件(Sharding Condition)、自增
主键信息(Auto increment Primary Key)、排序信息(Order By)、分组信息(Group By)以及分页信息
(Limit、Rownum、Top)。

三、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);

四、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;

五、SQL执行

Sharding-JDBC采用一套自动化的执行引擎,负责将路由和改写完成以后的真实SQL安全且高效发送到底层数据源
执行。 它不是简单地将SQL经过JDBC直接发送至数据源执行;也并不是直接将执行请求放入线程池去并发执行。它
更关注平衡数据源链接建立以及内存占用所产生的消耗,以及最大限度地合理利用并发等问题。 执行引擎的目标是
自动化的平衡资源控制与执行效率,他能在如下两种模式自适应切换:

内存限制模式

使用此模式的前提是, Sharding-JDBC对一次操做所耗费的数据库链接数量不作限制。 若是实际执行的SQL须要对
某数据库实例中的200张表作操做,则对每张表建立一个新的数据库链接,并经过多线程的方式并发处理,以达成
执行效率最大化。
链接限制模式
使用此模式的前提是,Sharding-JDBC严格控制对一次操做所耗费的数据库链接数量。 若是实际执行的SQL须要对
某数据库实例中的200张表作操做,那么只会建立惟一的数据库链接,并对其200张表串行处理。 若是一次操做中
的分片散落在不一样的数据库,仍然采用多线程处理对不一样库的操做,但每一个库的每次操做仍然只建立一个惟一的数
据库链接。
内存限制模式适用于OLAP操做,能够经过放宽对数据库链接的限制提高系统吞吐量; 链接限制模式适用于OLTP操
做,OLTP一般带有分片键,会路由到单一的分片,所以严格控制数据库链接,以保证在线系统数据库资源可以被
更多的应用所使用,是明智的选择。

六、结果归并

将从各个数据节点获取的多数据结果集,组合成为一个结果集并正确的返回至请求客户端,称为结果归并。
Sharding-JDBC支持的结果归并从功能上可分为遍历、排序、分组、分页和聚合5种类型,它们是组合而非互斥的
关系。
归并引擎的总体结构划分以下图。

img

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

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

img

下图则展示了进行next调用的时候,排序归并是如何进行的。 经过图中咱们能够看到,当进行第一次next调用
时,排在队列首位的t_score_0将会被弹出队列,而且将当前游标指向的数据值(也就是100)返回至查询客户端,
而且将游标下移一位以后,从新放入优先级队列。 而优先级队列也会根据t_score_0的当前数据结果集指向游标的
数据值(这里是90)进行排序,根据当前数值,t_score_0排列在队列的最后一位。 以前队列中排名第二的
t_score_2的数据结果集则自动排在了队列首位。
在进行第二次next时,只须要将目前排列在队列首位的t_score_2弹出队列,而且将其数据结果集游标指向的值返
回至客户端,并下移游标,继续加入队列排队,以此类推。 当一个结果集中已经没有数据了,则无需再次加入队
列。

img

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

总结

经过以上内容介绍,相信你们已经了解到Sharding-JDBC基础概念、核心功能以及执行原理。
基础概念:逻辑表,真实表,数据节点,绑定表,广播表,分片键,分片算法,分片策略,主键生成策略
核心功能:数据分片,读写分离
执行流程: SQL 解析 => 查询优化 => SQL路由 => SQL改写 => SQL执行 => 结果归并

高并发开发环境系列:springcloud环境

组件 连接地址
windows centos 虚拟机 安装&排坑 vagrant+java+springcloud+redis+zookeeper镜像下载(&制做详解))
centos mysql 安装&排坑 centos mysql 笔记(内含vagrant mysql 镜像)
linux kafka安装&排坑 kafka springboot (或 springcloud ) 整合
Linux openresty 安装 Linux openresty 安装
【必须】Linux Redis 安装(带视频) Linux Redis 安装(带视频)
【必须】Linux Zookeeper 安装(带视频) Linux Zookeeper 安装, 带视频
Windows Redis 安装(带视频) Windows Redis 安装(带视频)
RabbitMQ 离线安装(带视频) RabbitMQ 离线安装(带视频)
ElasticSearch 安装, 带视频 ElasticSearch 安装, 带视频
Nacos 安装(带视频) Nacos 安装(带视频)
【必须】Eureka Eureka 入门,带视频
【必须】springcloud Config 入门,带视频 springcloud Config 入门,带视频
【必须】SpringCloud 脚手架打包与启动 SpringCloud脚手架打包与启动
Linux 自启动 假死自启动 定时自启 Linux 自启动 假死启动

回到◀疯狂创客圈

疯狂创客圈 - Java高并发研习社群,为你们开启大厂之门

相关文章
相关标签/搜索