分庫分表 - 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高併發研習社羣,爲你們開啓大廠之門

相關文章
相關標籤/搜索