sharding-jdbc分庫(kù)分表-使用及原理

原文來(lái)自于:三不猴子

  • 垂直分表

    把一部分表字段放入一張表榆苞,另一部分放入其他的表。按照表字段的使用頻次分門別類的劃分脖卖。

    例如:在商品列表查詢時(shí)顺献,列表中只是展示部分字段塌鸯,同時(shí)這個(gè)列表查詢比詳情信息查詢更加高頻瞎嬉,并不需要把所有字段都展示,我們可以單獨(dú)把列表信息單獨(dú)拿出來(lái)當(dāng)一張表丸升。

    1. 為了避免IO爭(zhēng)搶并減少鎖表的幾率铆农,查看詳情的用戶與商品信息瀏覽互不影響
    2. 充分發(fā)揮熱門數(shù)據(jù)的操作效率,商品信息的操作的高效率不會(huì)被商品描述的低效率所拖累狡耻。

    當(dāng)表數(shù)據(jù)量很大時(shí),可以將表按字段切開猴凹,將熱門字段夷狰、冷門字段分開放置在不同表中,這些庫(kù)可以放在不同的存儲(chǔ)設(shè)備上郊霎,避免IO爭(zhēng)搶沼头。垂直切分帶來(lái)的性能提升主要集中在熱門數(shù)據(jù)的操作效率上,而且磁盤爭(zhēng)用情況減少书劝。

    • 大字段為什么效率更低
      1. 數(shù)據(jù)量大进倍,讀取需要更長(zhǎng)的時(shí)間。
      2. 數(shù)據(jù)比較大购对,跨頁(yè)存儲(chǔ)猾昆,磁盤存儲(chǔ)是一頁(yè)為單位,增加io次數(shù)骡苞。
      3. 數(shù)據(jù)庫(kù)是以行位單位加載至內(nèi)存垂蜗,同時(shí)也有內(nèi)存緩沖,數(shù)據(jù)較大命中緩沖的概率就小解幽。
  • 垂直分庫(kù)

    通過垂直分表達(dá)到一定的程度之后贴见,庫(kù)內(nèi)垂直分表只解決了單一表數(shù)據(jù)量過大的問題,但沒有將表分布到不同的服務(wù)器上躲株,因此每個(gè)表還是競(jìng)爭(zhēng)同一個(gè)物理機(jī)的CPU片部、內(nèi)存、網(wǎng)絡(luò)IO霜定、磁盤档悠。

    它帶來(lái)的提升是:

    1. 解決業(yè)務(wù)層面的耦合廊鸥,業(yè)務(wù)清晰
    2. 能對(duì)不同業(yè)務(wù)的數(shù)據(jù)進(jìn)行分級(jí)管理、維護(hù)站粟、監(jiān)控黍图、擴(kuò)展等
    3. 高并發(fā)場(chǎng)景下,垂直分庫(kù)一定程度的提升IO奴烙、數(shù)據(jù)庫(kù)連接數(shù)助被、降低單機(jī)硬件資源的瓶頸

    垂直分庫(kù)通過將表按業(yè)務(wù)分類,然后分布在不同數(shù)據(jù)庫(kù)切诀,并且可以將這些數(shù)據(jù)庫(kù)部署在不同服務(wù)器上揩环,從而達(dá)到多個(gè)服務(wù)器共同分?jǐn)倝毫Φ男Ч且廊粵]有解決單表數(shù)據(jù)量過大的問題幅虑。

  • 水平分庫(kù)

    我們已經(jīng)通過垂直分表丰滑,垂直分庫(kù)解決了一部分的性能問題,但是隨著業(yè)務(wù)的增長(zhǎng)倒庵,單表和單庫(kù)的性能越來(lái)越差褒墨,此時(shí)我們考慮將其中一半數(shù)據(jù)放一個(gè)庫(kù),這種就叫水平分庫(kù)(為啥叫水平分庫(kù)擎宝,因?yàn)榉殖鰜?lái)的庫(kù)的結(jié)構(gòu)是相同的郁妈。)。比如id為單數(shù)放在庫(kù)一绍申,id為雙數(shù)放在庫(kù)二噩咪。

  • 水平分表

    水平分庫(kù)可以解決單表數(shù)據(jù)過多的問題,但是過多的庫(kù)同時(shí)也會(huì)對(duì)運(yùn)維什么壓力會(huì)增大极阅,

水平分只是對(duì)數(shù)據(jù)進(jìn)行拆分胃碾,不改變結(jié)構(gòu)

在系統(tǒng)設(shè)計(jì)階段就應(yīng)該根據(jù)業(yè)務(wù)耦合松緊來(lái)確定垂直分庫(kù),垂直分表方案筋搏,在數(shù)據(jù)量及訪問壓力不是特別大的情況仆百,首先考慮緩存、讀寫分離拆又、索引技術(shù)等方案儒旬。若數(shù)據(jù)量極大,且持續(xù)增長(zhǎng)帖族,再考慮水平分庫(kù)水平分表

分庫(kù)分表帶來(lái)的問題

  • 事務(wù)一致性問題

    由于分庫(kù)分表把數(shù)據(jù)分布在不同庫(kù)甚至不同服務(wù)器栈源,不可避免會(huì)帶來(lái)分布式事務(wù)問題。

  • 跨節(jié)點(diǎn)關(guān)聯(lián)查詢

    在沒有分庫(kù)前竖般,我們檢索商品時(shí)可以通過以下SQL對(duì)店鋪信息進(jìn)行關(guān)聯(lián)查詢:

    SELECT p.*,r.[地理區(qū)域名稱],s.[店鋪名稱],s.[信譽(yù)]
    FROM [商品信息] p
    LEFT JOIN [地理區(qū)域] r ON p.[產(chǎn)地] = r.[地理區(qū)域編碼]
    LEFT JOIN [店鋪信息] s ON p.id = s.[所屬店鋪]
    WHERE...ORDER BY...LIMIT...
    

    但垂直分庫(kù)后[商品信息]和[店鋪信息]不在一個(gè)數(shù)據(jù)庫(kù)甚垦,甚至不在一臺(tái)服務(wù)器,無(wú)法進(jìn)行關(guān)聯(lián)查詢。

    解決方案:

    • 全局表:所謂全局表艰亮,就是有可能系統(tǒng)中所有模塊都可能會(huì)依賴到的一些表闭翩。比較類似我們理解的“數(shù)據(jù)字典”。為了避免跨庫(kù)join查詢迄埃,我們可以將這類表在其他每個(gè)數(shù)據(jù)庫(kù)中均保存一份疗韵。同時(shí),這類數(shù)據(jù)通常也很少發(fā)生修改(甚至幾乎不會(huì))侄非,所以也不用太擔(dān)心“一致性”問題蕉汪。

    • 字段冗余:這是一種典型的反范式設(shè)計(jì),在互聯(lián)網(wǎng)行業(yè)中比較常見逞怨,通常是為了性能來(lái)避免join查詢者疤。

      舉個(gè)電商業(yè)務(wù)中很簡(jiǎn)單的場(chǎng)景:

      “訂單表”中保存“賣家Id”的同時(shí),將賣家的“Name”字段也冗余叠赦,這樣查詢訂單詳情的時(shí)候就不需要再去查詢“賣家用戶表”驹马。

      字段冗余能帶來(lái)便利,是一種“空間換時(shí)間”的體現(xiàn)除秀。但其適用場(chǎng)景也比較有限糯累,比較適合依賴字段較少的情況。最復(fù)雜的還是數(shù)據(jù)一致性問題册踩,這點(diǎn)很難保證寇蚊,可以借助數(shù)據(jù)庫(kù)中的觸發(fā)器或者在業(yè)務(wù)代碼層面去保證。當(dāng)然棍好,也需要結(jié)合實(shí)際業(yè)務(wù)場(chǎng)景來(lái)看一致性的要求。就像上面例子允耿,如果賣家修改了Name之后借笙,是否需要在訂單信息中同步更新呢?

    • 數(shù)據(jù)組裝:分兩次查詢较锡,第一次先查出關(guān)聯(lián)數(shù)據(jù)id业稼,第二次根據(jù)id查詢出關(guān)聯(lián)數(shù)據(jù),然后將獲得的數(shù)據(jù)進(jìn)行字段拼裝蚂蕴。

    • ER分片:關(guān)系型數(shù)據(jù)庫(kù)中低散,如果能夠確定好表與表之間的關(guān)系,可以將有關(guān)聯(lián)關(guān)系的數(shù)據(jù)放在同一個(gè)分片上骡楼。

  • 跨節(jié)點(diǎn)分頁(yè)熔号、排序函數(shù)

    跨節(jié)點(diǎn)多庫(kù)進(jìn)行查詢時(shí),limit分頁(yè)鸟整、order by排序等問題引镊,就變得比較復(fù)雜了。需要先在不同的分片節(jié)點(diǎn)中將數(shù)據(jù)
    進(jìn)行排序并返回,然后將不同分片返回的結(jié)果集進(jìn)行匯總和再次排序弟头。
    如吩抓,進(jìn)行水平分庫(kù)后的商品庫(kù),按ID倒序排序分頁(yè)赴恨,取第一頁(yè):

image.png
  • 主鍵避重

    在分庫(kù)分表環(huán)境中疹娶,由于表中數(shù)據(jù)同時(shí)存在不同數(shù)據(jù)庫(kù)中,主鍵值平時(shí)使用的自增長(zhǎng)將無(wú)用武之地伦连,某個(gè)分區(qū)數(shù)據(jù)庫(kù)生成的ID無(wú)法保證全局唯一雨饺。因此需要單獨(dú)設(shè)計(jì)全局主鍵,以避免跨庫(kù)主鍵重復(fù)問題除师。

優(yōu)點(diǎn) 缺點(diǎn) 適用場(chǎng)景
使用UUID算法生成唯一id 無(wú)任何依賴 ID太長(zhǎng)沛膳,且不是數(shù)字類型 生成seesion_id
利用單機(jī)數(shù)據(jù)庫(kù)主鍵自增來(lái)生成唯一id 方便接入,單調(diào)遞增 生成效率低汛聚,強(qiáng)依賴于數(shù)據(jù)庫(kù)锹安,id是連續(xù)的 適用于并發(fā)量不高的業(yè)務(wù)。
多數(shù)據(jù)庫(kù)主鍵自增生成唯一id 方便接入倚舀,單調(diào)遞增叹哭,生成效率比單機(jī)數(shù)據(jù)庫(kù)高 不方便擴(kuò)容,強(qiáng)依賴于數(shù)據(jù)庫(kù)痕貌,id是連續(xù)的 適合分庫(kù)分表的架構(gòu)生成id
數(shù)據(jù)庫(kù)分段發(fā)號(hào)生成唯一id 效率高 強(qiáng)依賴于數(shù)據(jù)庫(kù)风罩,id是連續(xù)的 適合id生成并發(fā)量高的業(yè)務(wù),并且id連續(xù) 不會(huì)破壞信息安全的業(yè)務(wù)舵稠。
基于snowflake算法生成唯一id 效率高超升,運(yùn)行期間可以不依賴其他組件 id分布不均,對(duì)有些業(yè)務(wù)會(huì)造成數(shù)據(jù)傾斜的問題 適合id生成并發(fā)量高的業(yè)務(wù)

Sharding-JDBC介紹

它使用客戶端直連數(shù)據(jù)庫(kù)哺徊,以jar包形式提供服務(wù)室琢,無(wú)需額外部署和依賴,可理解為增強(qiáng)版的JDBC驅(qū)動(dòng)落追,完全兼容JDBC和各種
ORM框架盈滴。
Sharding-JDBC的核心功能為數(shù)據(jù)分片和讀寫分離,通過Sharding-JDBC轿钠,應(yīng)用可以透明的使用jdbc訪問已經(jīng)分庫(kù)分表巢钓、讀寫分離的多個(gè)數(shù)據(jù)源,而不用關(guān)心數(shù)據(jù)源的數(shù)量以及數(shù)據(jù)如何分布疗垛。
適用于任何基于Java的ORM框架症汹,如: Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。

image.png

sharding-jdbc流程

通過日志分析继谚,Sharding-JDBC在拿到用戶要執(zhí)行的sql之后干了哪些事兒:

  1. 解析sql烈菌,獲取片鍵值阵幸,比如order_id
  2. Sharding-JDBC通過規(guī)則配置 t_order_$->{order_id % 2 + 1},知道了當(dāng)order_id為偶數(shù)時(shí)芽世,應(yīng)該往t_order_1表插數(shù)據(jù)挚赊,為奇數(shù)時(shí),往t_order_2插數(shù)據(jù)济瓢。
  3. 于是Sharding-JDBC根據(jù)order_id的值改寫sql語(yǔ)句荠割,改寫后的SQ語(yǔ)句是真實(shí)所要執(zhí)行的SQL語(yǔ)句。
  4. 執(zhí)行改寫后的真實(shí)sql語(yǔ)句
  5. 將所有真正執(zhí)行sql的結(jié)果進(jìn)行匯總合并旺矾,返回蔑鹦。

Sharding-JDBC執(zhí)行原理

在了解Sharding-JDBC的執(zhí)行原理前,需要了解以下概念:

  • 邏輯表

    水平拆分的數(shù)據(jù)表的總稱箕宙。例:訂單數(shù)據(jù)表根據(jù)主鍵尾數(shù)拆分為10張表嚎朽,分別是 t_order_0 、 t_order_1 到t_order_9 柬帕,他們的邏輯表名為 t_order 哟忍。

  • 真實(shí)表

    在分片的數(shù)據(jù)庫(kù)中真實(shí)存在的物理表。即上個(gè)示例中的 t_order_0 到 t_order_9陷寝。

  • 數(shù)據(jù)節(jié)點(diǎn)

    數(shù)據(jù)分片的最小物理單元锅很。由數(shù)據(jù)源名稱和數(shù)據(jù)表組成,例:ds_0.t_order_0 凤跑。

  • 綁定表

    指分片規(guī)則一致的主表和子表爆安。例如: t_order 表和 t_order_item表,均按照 order_id 分片,綁定表之間的分區(qū)鍵完全相同仔引,則此兩張表互為綁定表關(guān)系扔仓。綁定表之間的多表關(guān)聯(lián)查詢不會(huì)出現(xiàn)笛卡爾積關(guān)聯(lián),關(guān)聯(lián)查詢效率將大大提升咖耘。舉例說(shuō)明当辐,如果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);
    

    在不配置綁定表關(guān)系時(shí),假設(shè)分片鍵 order_id 將數(shù)值10路由至第0片鲤看,將數(shù)值11路由至第1片,那么路由后的SQL應(yīng)該為4條耍群,它們呈現(xiàn)為笛卡爾積:

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

    在配置綁定表關(guān)系后义桂,路由的SQL應(yīng)該為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);
    
  • 廣播表

    指所有的分片數(shù)據(jù)源中都存在的表,表結(jié)構(gòu)和表中的數(shù)據(jù)在每個(gè)數(shù)據(jù)庫(kù)中均完全一致蹈垢。適用于數(shù)據(jù)量不大且需要與海量數(shù)據(jù)的表進(jìn)行關(guān)聯(lián)查詢的場(chǎng)景慷吊,例如:字典表。

  • 分片鍵

    用于分片的數(shù)據(jù)庫(kù)字段曹抬,是將數(shù)據(jù)庫(kù)(表)水平拆分的關(guān)鍵字段溉瓶。例:將訂單表中的訂單主鍵的尾數(shù)取模分片,則訂單主鍵為分片字段。SQL中如果無(wú)分片字段堰酿,將執(zhí)行全路由疾宏,性能較差。 除了對(duì)單分片字段的支持触创,ShardingJdbc也支持根據(jù)多個(gè)字段進(jìn)行分片坎藐。

  • 分片算法

    包含分片鍵和分片算法,由于分片算法的獨(dú)立性哼绑,將其獨(dú)立抽離岩馍。真正可用于分片操作的是分片鍵 + 分片算法,也就是分片策略抖韩。內(nèi)置的分片策略大致可分為尾數(shù)取模蛀恩、哈希、范圍茂浮、標(biāo)簽双谆、時(shí)間等。由用戶方配置的分片策略則更加靈活励稳,常用的使用行表達(dá)式配置分片策略佃乘,它采用Groovy表達(dá)式表示,如: t_user_$->{u_id % 8} 表示t_user表根據(jù)u_id模8驹尼,而分成8張表趣避,表名稱為 t_user_0 到 t_user_7 。

  • 自增主鍵生成策略

    通過在客戶端生成自增主鍵替換以數(shù)據(jù)庫(kù)原生自增主鍵的方式新翎,做到分布式主鍵無(wú)重復(fù)程帕。

SQL解析

當(dāng)Sharding-JDBC接受到一條SQL語(yǔ)句時(shí),會(huì)陸續(xù)執(zhí)行 SQL解析 => 查詢優(yōu)化 => SQL路由 => SQL改寫 => SQL執(zhí)行 =>結(jié)果歸并 地啰,最終返回執(zhí)行結(jié)果愁拭。

image.png

SQL解析過程分為詞法解析和語(yǔ)法解析。 詞法解析器用于將SQL拆解為不可再分的原子符號(hào)亏吝,稱為Token岭埠。并根據(jù)不同數(shù)據(jù)庫(kù)方言所提供的字典,將其歸類為關(guān)鍵字蔚鸥,表達(dá)式惜论,字面量和操作符。 再使用語(yǔ)法解析器將SQL轉(zhuǎn)換為抽象語(yǔ)法樹止喷。
例如馆类,以下SQL:

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

解析之后的為抽象語(yǔ)法樹見下圖:

image.png

通過對(duì)抽象語(yǔ)法樹的遍歷去提煉分片所需的上下文,并標(biāo)記有可能需要SQL改寫(后邊介紹)的位置弹谁。供分片使用的解析上下文包含查詢選擇項(xiàng)(Select Items)乾巧、表信息(Table)句喜、分片條件(Sharding Condition)、自增主鍵信息(Auto increment Primary Key)沟于、排序信息(Order By)咳胃、分組信息(Group By)以及分頁(yè)信息(Limit、Rownum社裆、Top)拙绊。

SQL路由

SQL路由就是把針對(duì)邏輯表的數(shù)據(jù)操作映射到對(duì)數(shù)據(jù)結(jié)點(diǎn)操作的過程。

根據(jù)解析上下文匹配數(shù)據(jù)庫(kù)和表的分片策略泳秀,并生成路由路徑标沪。

對(duì)于攜帶分片鍵的SQL,根據(jù)分片鍵操作符不同可以劃分為單片路由(分片鍵的操作符是等號(hào))嗜傅、多片路由(分片鍵的操作符是IN)和范圍路由(分片鍵的操作符是BETWEEN)金句,不攜帶分片鍵的SQL則采用廣播路由。

標(biāo)準(zhǔn)路由

標(biāo)準(zhǔn)路由是 ShardingSphere 最為推薦使用的分片方式吕嘀,它的適用范圍是不包含關(guān)聯(lián)查詢或僅包含綁定表之間關(guān)聯(lián)查詢的 SQL违寞。 當(dāng)分片運(yùn)算符是等于號(hào)時(shí),路由結(jié)果將落入單庫(kù)(表)偶房,當(dāng)分片運(yùn)算符是 BETWEEN 或 IN 時(shí)趁曼,則路由結(jié)果不一定落入唯一的庫(kù)(表),因此一條邏輯 SQL 最終可能被拆分為多條用于執(zhí)行的真實(shí) SQL棕洋。 舉例說(shuō)明挡闰,如果按照 order_id 的奇數(shù)和偶數(shù)進(jìn)行數(shù)據(jù)分片,一個(gè)單表查詢的 SQL 如下:

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

那么路由的結(jié)果應(yīng)為:

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

綁定表的關(guān)聯(lián)查詢與單表查詢復(fù)雜度和性能相當(dāng)掰盘。舉例說(shuō)明摄悯,如果一個(gè)包含綁定表的關(guān)聯(lián)查詢的 SQL 如下:

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

那么路由的結(jié)果應(yīng)為:

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 拆分的數(shù)目與單表是一致的愧捕。

笛卡爾路由

笛卡爾路由是最復(fù)雜的情況奢驯,它無(wú)法根據(jù)綁定表的關(guān)系定位分片規(guī)則,因此非綁定表之間的關(guān)聯(lián)查詢需要拆解為笛卡爾積組合執(zhí)行次绘。 如果上個(gè)示例中的 SQL 并未配置綁定表關(guān)系瘪阁,那么路由的結(jié)果應(yīng)為:

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

笛卡爾路由查詢性能較低,需謹(jǐn)慎使用邮偎。全庫(kù)表路由對(duì)于不攜帶分片鍵的SQL罗洗,則采取廣播路由的方式。根據(jù)SQL類型又可以劃分為全庫(kù)表路由钢猛、全庫(kù)路由、全實(shí)例路由轩缤、單播路由和阻斷路由這5種類型命迈。其中全庫(kù)表路由用于處理對(duì)數(shù)據(jù)庫(kù)中與其邏輯表相關(guān)的所有真實(shí)表的操作贩绕,主要包括不帶分片鍵的DQL(數(shù)據(jù)查詢)和DML(數(shù)據(jù)操縱),以及DDL(數(shù)據(jù)定義)等壶愤。例如:

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

則會(huì)遍歷所有數(shù)據(jù)庫(kù)中的所有表淑倾,逐一匹配邏輯表和真實(shí)表名,能夠匹配得上則執(zhí)行征椒。路由后成為

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娇哆,并不能夠直接在真實(shí)的數(shù)據(jù)庫(kù)中執(zhí)行,SQL改寫用于將邏輯SQL改寫為在真實(shí)數(shù)據(jù)庫(kù)中可以正確執(zhí)行的SQL勃救。
如一個(gè)簡(jiǎn)單的例子碍讨,若邏輯SQL為:

SELECT order_id FROM t_order WHERE order_id=1;

假設(shè)該SQL配置分片鍵order_id,并且order_id=1的情況蒙秒,將路由至分片表1勃黍。那么改寫之后的SQL應(yīng)該為:

SELECT order_id FROM t_order_1 WHERE order_id=1;

補(bǔ)列

再比如,Sharding-JDBC需要在結(jié)果歸并時(shí)獲取相應(yīng)數(shù)據(jù)晕讲,但該數(shù)據(jù)并未能通過查詢的SQL返回覆获。 這種情況主要是針對(duì)GROUP BY和ORDER BY。結(jié)果歸并時(shí)瓢省,需要根據(jù) GROUP BY 和 ORDER BY 的字段項(xiàng)進(jìn)行分組和排序弄息,但如果原始SQL的選擇項(xiàng)中若并未包含分組項(xiàng)或排序項(xiàng),則需要對(duì)原始SQL進(jìn)行改寫勤婚。 先看一下原始SQL中帶有結(jié)果歸并所需信息的場(chǎng)景:

SELECT order_id, user_id FROM t_order ORDER BY user_id;

由于使用user_id進(jìn)行排序摹量,在結(jié)果歸并中需要能夠獲取到user_id的數(shù)據(jù),而上面的SQL是能夠獲取到user_id數(shù)據(jù)的蛔六,因此無(wú)需補(bǔ)列荆永。
如果選擇項(xiàng)中不包含結(jié)果歸并時(shí)所需的列,則需要進(jìn)行補(bǔ)列国章,如以下SQL:

SELECT order_id FROM t_order ORDER BY user_id;

由于原始SQL中并不包含需要在結(jié)果歸并中需要獲取的user_id具钥,因此需要對(duì)SQL進(jìn)行補(bǔ)列改寫。補(bǔ)列之后的SQL是:

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

補(bǔ)列的另一種情況是使用 AVG 聚合函數(shù)液兽。在分布式的場(chǎng)景中骂删,使用 avg1 + avg2 + avg3 / 3 計(jì)算平均值并不正確锭吨,需要改寫為 (sum1 + sum2 + sum3) / (count1 + count2 + count3)梯码。 這就需要將包含 AVG 的 SQL 改寫為 SUM 和 COUNT,并在結(jié)果歸并時(shí)重新計(jì)算平均值薇正。例如以下 SQL:

SELECT AVG(price) FROM t_order WHERE user_id=1;

需要改寫為:

SELECT COUNT(price) AS AVG_DERIVED_COUNT_0, SUM(price) AS AVG_DERIVED_SUM_0 FROM t_order WHERE user_id=1;

然后才能夠通過結(jié)果歸并正確的計(jì)算平均值柑晒。

分頁(yè)修正

從多個(gè)數(shù)據(jù)庫(kù)獲取分頁(yè)數(shù)據(jù)與單數(shù)據(jù)庫(kù)的場(chǎng)景是不同的欧瘪。 假設(shè)每 10 條數(shù)據(jù)為一頁(yè),取第 2 頁(yè)數(shù)據(jù)匙赞。在分片環(huán)境下獲取 LIMIT 10, 10佛掖,歸并之后再根據(jù)排序條件取出前 10 條數(shù)據(jù)是不正確的妖碉。 舉例說(shuō)明,若 SQL 為:

SELECT score FROM t_score ORDER BY score DESC LIMIT 1, 2;

下圖展示了不進(jìn)行 SQL 的改寫的分頁(yè)執(zhí)行結(jié)果芥被。

image

通過圖中所示欧宜,想要取得兩個(gè)表中共同的按照分?jǐn)?shù)排序的第 2 條和第 3 條數(shù)據(jù),應(yīng)該是 9590拴魄。 由于執(zhí)行的 SQL 只能從每個(gè)表中獲取第 2 條和第 3 條數(shù)據(jù)冗茸,即從 t_score_0 表中獲取的是 9080;從 t_score_1 表中獲取的是 8575匹中。 因此進(jìn)行結(jié)果歸并時(shí)夏漱,只能從獲取的 9080职员,8575 之中進(jìn)行歸并麻蹋,那么結(jié)果歸并無(wú)論怎么實(shí)現(xiàn),都不可能獲得正確的結(jié)果焊切。

正確的做法是將分頁(yè)條件改寫為 LIMIT 0, 3扮授,取出所有前兩頁(yè)數(shù)據(jù),再結(jié)合排序條件計(jì)算出正確的數(shù)據(jù)专肪。 下圖展示了進(jìn)行 SQL 改寫之后的分頁(yè)執(zhí)行結(jié)果刹勃。

image

越獲取偏移量位置靠后數(shù)據(jù),使用 LIMIT 分頁(yè)方式的效率就越低嚎尤。 有很多方法可以避免使用 LIMIT 進(jìn)行分頁(yè)荔仁。比如構(gòu)建行記錄數(shù)量與行偏移量的二級(jí)索引,或使用上次分頁(yè)數(shù)據(jù)結(jié)尾 ID 作為下次查詢條件的分頁(yè)方式等芽死。

分頁(yè)信息修正時(shí)乏梁,如果使用占位符的方式書寫 SQL,則只需要改寫參數(shù)列表即可关贵,無(wú)需改寫 SQL 本身遇骑。

SQL執(zhí)行

內(nèi)存限制模式

使用此模式的前提是,ShardingSphere 對(duì)一次操作所耗費(fèi)的數(shù)據(jù)庫(kù)連接數(shù)量不做限制揖曾。 如果實(shí)際執(zhí)行的 SQL 需要對(duì)某數(shù)據(jù)庫(kù)實(shí)例中的 200 張表做操作落萎,則對(duì)每張表創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù)連接,并通過多線程的方式并發(fā)處理炭剪,以達(dá)成執(zhí)行效率最大化练链。 并且在 SQL 滿足條件情況下,優(yōu)先選擇流式歸并奴拦,以防止出現(xiàn)內(nèi)存溢出或避免頻繁垃圾回收情況媒鼓。

連接限制模式

使用此模式的前提是,ShardingSphere 嚴(yán)格控制對(duì)一次操作所耗費(fèi)的數(shù)據(jù)庫(kù)連接數(shù)量。 如果實(shí)際執(zhí)行的 SQL 需要對(duì)某數(shù)據(jù)庫(kù)實(shí)例中的 200 張表做操作绿鸣,那么只會(huì)創(chuàng)建唯一的數(shù)據(jù)庫(kù)連接瓷产,并對(duì)其 200 張表串行處理。 如果一次操作中的分片散落在不同的數(shù)據(jù)庫(kù)枚驻,仍然采用多線程處理對(duì)不同庫(kù)的操作,但每個(gè)庫(kù)的每次操作仍然只創(chuàng)建一個(gè)唯一的數(shù)據(jù)庫(kù)連接株旷。 這樣即可以防止對(duì)一次請(qǐng)求對(duì)數(shù)據(jù)庫(kù)連接占用過多所帶來(lái)的問題再登。該模式始終選擇內(nèi)存歸并。

內(nèi)存限制模式適用于 OLAP 操作晾剖,可以通過放寬對(duì)數(shù)據(jù)庫(kù)連接的限制提升系統(tǒng)吞吐量锉矢; 連接限制模式適用于 OLTP 操作,OLTP 通常帶有分片鍵齿尽,會(huì)路由到單一的分片沽损,因此嚴(yán)格控制數(shù)據(jù)庫(kù)連接,以保證在線系統(tǒng)數(shù)據(jù)庫(kù)資源能夠被更多的應(yīng)用所使用循头,是明智的選擇绵估。

自動(dòng)化執(zhí)行引擎

兩種模式的切換交由靜態(tài)的初始化配置,是缺乏靈活應(yīng)對(duì)能力的卡骂。在實(shí)際的使用場(chǎng)景中国裳,面對(duì)不同 SQL 以及占位符參數(shù),每次的路由結(jié)果是不同的全跨。 這就意味著某些操作可能需要使用內(nèi)存歸并缝左,而某些操作則可能選擇流式歸并更優(yōu),具體采用哪種方式不應(yīng)該由用戶在 ShardingSphere 啟動(dòng)之前配置好浓若,而是應(yīng)該根據(jù) SQL 和占位符參數(shù)的場(chǎng)景渺杉,來(lái)動(dòng)態(tài)的決定連接模式。用戶無(wú)需了解所謂的內(nèi)存限制模式和連接限制模式是什么挪钓,而是交由執(zhí)行引擎根據(jù)當(dāng)前場(chǎng)景自動(dòng)選擇最優(yōu)的執(zhí)行方案是越。

自動(dòng)化執(zhí)行引擎將連接模式的選擇粒度細(xì)化至每一次 SQL 的操作。 針對(duì)每次 SQL 請(qǐng)求诵原,自動(dòng)化執(zhí)行引擎都將根據(jù)其路由結(jié)果英妓,進(jìn)行實(shí)時(shí)的演算和權(quán)衡,并自主地采用恰當(dāng)?shù)倪B接模式執(zhí)行绍赛,以達(dá)到資源控制和效率的最優(yōu)平衡蔓纠。

  • 準(zhǔn)備階段

    此階段用于準(zhǔn)備執(zhí)行的數(shù)據(jù)。它分為結(jié)果集分組和執(zhí)行單元?jiǎng)?chuàng)建兩個(gè)步驟吗蚌。

    1. 將 SQL 的路由結(jié)果按照數(shù)據(jù)源的名稱進(jìn)行分組腿倚。
    2. 通過下圖的公式,可以獲得每個(gè)數(shù)據(jù)庫(kù)實(shí)例在 maxConnectionSizePerQuery 的允許范圍內(nèi)蚯妇,每個(gè)連接需要執(zhí)行的 SQL 路由結(jié)果組敷燎,并計(jì)算出本次請(qǐng)求的最優(yōu)連接模式暂筝。
image.png
在 maxConnectionSizePerQuery 允許的范圍內(nèi),當(dāng)一個(gè)連接需要執(zhí)行的請(qǐng)求數(shù)量大于 1 時(shí)硬贯,意味著當(dāng)前的數(shù)據(jù)庫(kù)連接無(wú)法持有相應(yīng)的數(shù)據(jù)結(jié)果集焕襟,則必須采用內(nèi)存歸并; 反之饭豹,當(dāng)一個(gè)連接需要執(zhí)行的請(qǐng)求數(shù)量等于 1 時(shí)鸵赖,意味著當(dāng)前的數(shù)據(jù)庫(kù)連接可以持有相應(yīng)的數(shù)據(jù)結(jié)果集,則可以采用流式歸并拄衰。

**每一次的連接模式的選擇它褪,是針對(duì)每一個(gè)物理數(shù)據(jù)庫(kù)的**。在同一次查詢中翘悉,如果路由至一個(gè)以上的數(shù)據(jù)庫(kù)茫打,每個(gè)數(shù)據(jù)庫(kù)的連接模式不一定一樣,它們可能是混合存在的形態(tài)妖混。

當(dāng)數(shù)據(jù)源使用數(shù)據(jù)庫(kù)連接池等控制數(shù)據(jù)庫(kù)連接數(shù)量的技術(shù)時(shí)老赤,在獲取數(shù)據(jù)庫(kù)連接時(shí),如果不妥善處理并發(fā)源葫,則有一定幾率發(fā)生死鎖诗越。 在多個(gè)請(qǐng)求相互等待對(duì)方釋放數(shù)據(jù)庫(kù)連接資源時(shí),將會(huì)產(chǎn)生饑餓等待息堂,造成交叉的死鎖問題嚷狞。

假設(shè)一次查詢需要在某一數(shù)據(jù)源上獲取兩個(gè)數(shù)據(jù)庫(kù)連接,并路由至同一個(gè)數(shù)據(jù)庫(kù)的兩個(gè)分表查詢荣堰。 則有可能出現(xiàn)查詢 A 已獲取到該數(shù)據(jù)源的 1 個(gè)數(shù)據(jù)庫(kù)連接床未,并等待獲取另一個(gè)數(shù)據(jù)庫(kù)連接;而查詢 B 也已經(jīng)在該數(shù)據(jù)源上獲取到的一個(gè)數(shù)據(jù)庫(kù)連接振坚,并同樣等待另一個(gè)數(shù)據(jù)庫(kù)連接的獲取薇搁。 如果數(shù)據(jù)庫(kù)連接池的允許最大連接數(shù)是 2,那么這 2 個(gè)查詢請(qǐng)求將永久的等待下去渡八。下圖描繪了死鎖的情況啃洋。
image.png
ShardingSphere 為了避免死鎖的出現(xiàn),在獲取數(shù)據(jù)庫(kù)連接時(shí)進(jìn)行了同步處理屎鳍。 它在創(chuàng)建執(zhí)行單元時(shí)宏娄,以原子性的方式一次性獲取本次 SQL 請(qǐng)求所需的全部數(shù)據(jù)庫(kù)連接,杜絕了每次查詢請(qǐng)求獲取到部分資源的可能逮壁。 由于對(duì)數(shù)據(jù)庫(kù)的操作非常頻繁孵坚,每次獲取數(shù)據(jù)庫(kù)連接時(shí)時(shí)都進(jìn)行鎖定,會(huì)降低 ShardingSphere 的并發(fā)。因此卖宠,ShardingSphere 在這里進(jìn)行了 2 點(diǎn)優(yōu)化:

1. 避免鎖定一次性只需要獲取1個(gè)數(shù)據(jù)庫(kù)連接的操作巍杈。因?yàn)槊看蝺H需要獲取 1 個(gè)連接,則不會(huì)發(fā)生兩個(gè)請(qǐng)求相互等待的場(chǎng)景扛伍,無(wú)需鎖定筷畦。 對(duì)于大部分 OLTP 的操作,都是使用分片鍵路由至唯一的數(shù)據(jù)節(jié)點(diǎn)刺洒,這會(huì)使得系統(tǒng)變?yōu)橥耆珶o(wú)鎖的狀態(tài)汁咏,進(jìn)一步提升了并發(fā)效率。 除了路由至單分片的情況作媚,讀寫分離也在此范疇之內(nèi)。
2. 僅針對(duì)內(nèi)存限制模式時(shí)才進(jìn)行資源鎖定帅刊。在使用連接限制模式時(shí)纸泡,所有的查詢結(jié)果集將在裝載至內(nèi)存之后釋放掉數(shù)據(jù)庫(kù)連接資源,因此不會(huì)產(chǎn)生死鎖等待的問題赖瞒。
  • 執(zhí)行階段

    該階段用于真正的執(zhí)行 SQL女揭,它分為分組執(zhí)行和歸并結(jié)果集生成兩個(gè)步驟。

    分組執(zhí)行將準(zhǔn)備執(zhí)行階段生成的執(zhí)行單元分組下發(fā)至底層并發(fā)執(zhí)行引擎栏饮,并針對(duì)執(zhí)行過程中的每個(gè)關(guān)鍵步驟發(fā)送事件吧兔。 如:執(zhí)行開始事件、執(zhí)行成功事件以及執(zhí)行失敗事件袍嬉。執(zhí)行引擎僅關(guān)注事件的發(fā)送境蔼,它并不關(guān)心事件的訂閱者。 ShardingSphere 的其他模塊伺通,如:分布式事務(wù)箍土、調(diào)用鏈路追蹤等,會(huì)訂閱感興趣的事件罐监,并進(jìn)行相應(yīng)的處理吴藻。

    ShardingSphere 通過在執(zhí)行準(zhǔn)備階段的獲取的連接模式,生成內(nèi)存歸并結(jié)果集或流式歸并結(jié)果集弓柱,并將其傳遞至結(jié)果歸并引擎沟堡,以進(jìn)行下一步的工作。

    執(zhí)行引擎的整體結(jié)構(gòu)劃分如下圖所示矢空。

image.png
## 結(jié)果歸并

將從各個(gè)數(shù)據(jù)節(jié)點(diǎn)獲取的多數(shù)據(jù)結(jié)果集航罗,組合成為一個(gè)結(jié)果集并正確的返回至請(qǐng)求客戶端,稱為結(jié)果歸并妇多。

Sharding-JDBC支持的結(jié)果歸并從功能上可分為遍歷伤哺、排序、分組、分頁(yè)和聚合5種類型立莉,它們是組合而非互斥的關(guān)系绢彤。歸并引擎的整體結(jié)構(gòu)劃分如下圖。
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蜓耻,一起剝皮案震驚了整個(gè)濱河市茫舶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刹淌,老刑警劉巖饶氏,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異有勾,居然都是意外死亡疹启,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蔼卡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)喊崖,“玉大人,你說(shuō)我怎么就攤上這事雇逞』缍” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵塘砸,是天一觀的道長(zhǎng)节仿。 經(jīng)常有香客問我,道長(zhǎng)掉蔬,這世上最難降的妖魔是什么廊宪? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮女轿,結(jié)果婚禮上挤忙,老公的妹妹穿的比我還像新娘。我一直安慰自己谈喳,他們只是感情好册烈,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著婿禽,像睡著了一般赏僧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扭倾,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天淀零,我揣著相機(jī)與錄音,去河邊找鬼膛壹。 笑死驾中,一個(gè)胖子當(dāng)著我的面吹牛唉堪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肩民,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼唠亚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了持痰?” 一聲冷哼從身側(cè)響起灶搜,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎工窍,沒想到半個(gè)月后割卖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡患雏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年鹏溯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淹仑。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剿涮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出攻人,到底是詐尸還是另有隱情,我是刑警寧澤悬槽,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布怀吻,位于F島的核電站,受9級(jí)特大地震影響初婆,放射性物質(zhì)發(fā)生泄漏蓬坡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一磅叛、第九天 我趴在偏房一處隱蔽的房頂上張望屑咳。 院中可真熱鬧,春花似錦弊琴、人聲如沸兆龙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)紫皇。三九已至,卻和暖如春腋寨,著一層夾襖步出監(jiān)牢的瞬間聪铺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工萄窜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铃剔,地道東北人撒桨。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像键兜,于是被迫代替她去往敵國(guó)和親凤类。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容