1.join 概述

Join 絕對(duì)是關(guān)系型數(shù)據(jù)庫(kù)中最常用一個(gè)特性,然而在分布式環(huán)境中,跨分片的 join 確是最復(fù)雜的,最難解決一個(gè)問(wèn)題。
下面我們簡(jiǎn)單介紹下各種 Join 操作。
INNER JOIN
內(nèi)連接,也叫等值連接,inner join 產(chǎn)生同時(shí)符合 A 表和 B 表的一組數(shù)據(jù)。
如圖:

LEFT JOIN
左連接從 A 表(左)產(chǎn)生一套完整的記錄,與匹配的 B 表記錄(右表) .如果沒(méi)有匹配,右側(cè)將包含 null,在 Mysql 中等同于 left outer join。
如圖:

RIGHT JOIN
同 Left join,AB 表互換即可。
CROSS JOIN
交叉連接,得到的結(jié)果是兩個(gè)表的乘積,即笛卡爾積。笛卡爾(Descartes)乘積又叫直積。假設(shè)集合A={a,b},集合 B={0,1,2},則兩個(gè)集合的笛卡爾積為{(a,0),(a,1),(a,2),(b,0),(b,1), (b,2)}??梢詳U(kuò)展到多個(gè)集合的情況。類似的例子有,如果 A 表示某學(xué)校學(xué)生的集合,B 表示該學(xué)校所有課程的集合,則 A 與 B 的笛卡爾積表示所有可能的選課情況。
FULL JOIN
全連接產(chǎn)生的所有記錄(雙方匹配記錄)在表 A 和表 B。如果沒(méi)有匹配,則對(duì)面將包含 null。

性能建議
盡量避免使用 Left join 或 Right join,而用 Inner join
在使用 Left join 或 Right join 時(shí),ON 會(huì)優(yōu)先執(zhí)行,where 條件在最后執(zhí)行,所以在使用過(guò)程中,條件盡可能的在 ON 語(yǔ)句中判斷,減少 where 的執(zhí)行,少用子查詢,而用 join。
Mycat 目前版本支持跨分片的 join,主要實(shí)現(xiàn)的方式有四種。
全局表,ER 分片,catletT(人工智能)和 ShareJoin,ShareJoin 在開(kāi)發(fā)版中支持,前面三種方式 1.3.0.1 支持
2.全局表
一個(gè)真實(shí)的業(yè)務(wù)系統(tǒng)中,往往存在大量的類似字典表的表格,它們與業(yè)務(wù)表之間可能有關(guān)系,這種關(guān)系,可以理解為“標(biāo)簽”,而不應(yīng)理解為通常的“主從關(guān)系”,這些表基本上很少變動(dòng),可以根據(jù)主鍵 ID 進(jìn)行緩存,下面這張圖說(shuō)明了一個(gè)典型的“標(biāo)簽關(guān)系”圖:

在分片的情況下,當(dāng)業(yè)務(wù)表因?yàn)橐?guī)模而進(jìn)行分片以后,業(yè)務(wù)表與這些附屬的字典表之間的關(guān)聯(lián),就成了比較棘手的問(wèn)題,考慮到字典表具有以下幾個(gè)特性:
? 變動(dòng)不頻繁
? 數(shù)據(jù)量總體變化不大
? 數(shù)據(jù)規(guī)模不大,很少有超過(guò)數(shù)十萬(wàn)條記錄。
鑒于此,MyCAT 定義了一種特殊的表,稱之為“全局表”,全局表具有以下特性:
? 全局表的插入、更新操作會(huì)實(shí)時(shí)在所有節(jié)點(diǎn)上執(zhí)行,保持各個(gè)分片的數(shù)據(jù)一致性
? 全局表的查詢操作,只從一個(gè)節(jié)點(diǎn)獲取
? 全局表可以跟任何一個(gè)表進(jìn)行 JOIN 操作
將字典表或者符合字典表特性的一些表定義為全局表,則從另外一個(gè)方面,很好的解決了數(shù)據(jù) JOIN 的難題。
通過(guò)全局表+基于 E-R 關(guān)系的分片策略,MyCAT 可以滿足 80%以上的企業(yè)應(yīng)用開(kāi)發(fā)。
配置
全局表配置比較簡(jiǎn)單,不用寫(xiě) Rule 規(guī)則,如下配置即可:
<table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" />
需要注意的是,全局表每個(gè)分片節(jié)點(diǎn)上都要有運(yùn)行創(chuàng)建表的 DDL 語(yǔ)句
3.ER Join
MyCAT 借鑒了 NewSQL 領(lǐng)域的新秀 Foundation DB 的設(shè)計(jì)思路,F(xiàn)oundation DB 創(chuàng)新性的提出了 TableGroup 的概念,其將子表的存儲(chǔ)位置依賴于主表,并且物理上緊鄰存放,因此徹底解決了 JION 的效率和性能問(wèn)題,根據(jù)這一思路,提出了基于 E-R 關(guān)系的數(shù)據(jù)分片策略,子表的記錄與所關(guān)聯(lián)的父表記錄存放在同一個(gè)數(shù)據(jù)分片上。
customer 采用 sharding-by-intfile 這個(gè)分片策略,分片在 dn1,dn2 上,orders 依賴父表進(jìn)行分片,兩個(gè)表的關(guān)聯(lián)關(guān)系為 orders.customer_id=customer.id。于是數(shù)據(jù)分片和存儲(chǔ)的示意圖如下:

這樣一來(lái),分片 Dn1 上的的 customer 與 Dn1 上的 orders 就可以進(jìn)行局部的 JOIN 聯(lián)合,Dn2 上也如此,再合并兩個(gè)節(jié)點(diǎn)的數(shù)據(jù)即可完成整體的 JOIN,試想一下,每個(gè)分片上 orders 表有 100 萬(wàn)條,則 10 個(gè)分片就有 1 個(gè)億,基于 E-R 映射的數(shù)據(jù)分片模式,基本上解決了 80%以上的企業(yè)應(yīng)用所面臨的問(wèn)題。
配置
以上述例子為例,schema.xml 中定義如下的分片配置:
<table name="customer" dataNode="dn1,dn2" rule="sharding-by-intfile">
<childTable name="orders" joinKey="customer_id" parentKey="id"/>
</table>
4.Share join
ShareJoin 是一個(gè)簡(jiǎn)單的跨分片 Join,基于 HBT 的方式實(shí)現(xiàn)。
目前支持 2 個(gè)表的 join,原理就是解析 SQL 語(yǔ)句,拆分成單表的 SQL 語(yǔ)句執(zhí)行,然后把各個(gè)節(jié)點(diǎn)的數(shù)據(jù)匯集。
配置
支持任意配置的 A,B 表如:
A,B 的 dataNode 相同:
<table name="A" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
<table name="B" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
A,B 的 dataNode 不同
<table name="A" dataNode="dn1,dn2 " rule="auto-sharding-long" />
<table name="B" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
或
<table name="A" dataNode="dn1 " rule="auto-sharding-long" />
<table name="B" dataNode=" dn2,dn3" rule="auto-sharding-long" />
代碼測(cè)試
先把表 company 從全局表修改下配置
<table name="company" primaryKey="ID" dataNode="dn1,dn2,dn3" rule="mod-long" />
重新插入數(shù)據(jù)


下面可以看下普通的 join 和 sharejoin 的區(qū)別:





5.catlet(人工智能)
解決跨分片的 SQL JOIN 的問(wèn)題,遠(yuǎn)比想象的復(fù)雜,而且往往無(wú)法實(shí)現(xiàn)高效的處理,既然如此,就依靠人工的智力,去編程解決業(yè)務(wù)系統(tǒng)中特定幾個(gè)必須跨分片的 SQL 的 JOIN 邏輯,MyCAT 提供特定的 API 供程序員調(diào)用,這就是 MyCAT 創(chuàng)新性的思路——人工智能。
以一個(gè)跨節(jié)點(diǎn)的 SQL 為例
Select a.id,a.name,b.title from a,b where a.id=b.id
其中 a 在分片 1,2,3 上,b 在 4,5,6 上,需要把數(shù)據(jù)全部拉到本地(MyCAT 服務(wù)器),執(zhí)行 JOIN 邏輯,具體過(guò)程如下(只是一種可能的執(zhí)行邏輯):
EngineCtx ctx=new EngineCtx();//包含 MyCat.SQLEngine
String sql=,“ select a.id ,a.name from a ” ;
//在 a 表所在的所有分片上順序執(zhí)行下面的本地 SQL
ctx.executeNativeSQLSequnceJob(allAnodes,new DirectDBJoinHandler());
DirectDBJoinHandler 類是一個(gè)回調(diào)類,負(fù)責(zé)處理 SQL 執(zhí)行過(guò)程中返回的數(shù)據(jù)包,這里的這個(gè)類,主要目的是用 a 表返回的 ID 信息,去 b 表上查詢對(duì)于的記錄,做實(shí)時(shí)的關(guān)聯(lián):
DirectDBJoinHandler{
Private HashMap rows;//Key 為 id,value 為一行記錄的 Column 原始 Byte 數(shù)組,這里是a.id,a.name,b.title 這三個(gè)要輸出的字段
Public Boolean onHeader(byte[] header){
//保存 Header 信息,用于從 Row 中獲取 Field 字段值
}
Public Boolean onRowData(byte[] rowData){
String id=getColumnAsString(“ id” );
//放入結(jié)果集,b.title 字段未知,所以先空著
rows.put(getColumnRawBytes(“ id” ),rowData);
//滿 1000 條,發(fā)送一個(gè)查詢請(qǐng)求
String sql=” select b.id, b.name from b where id in (………….)” ;
//此 SQL 在 B 的所有節(jié)點(diǎn)上并發(fā)執(zhí)行,返回的結(jié)果直接輸出到客戶端
ctx.executeNativeSQLParallJob(allBNodes,sql ,new MyRowOutPutDataHandler(rows));
}
Public Boolean onRowFinished(){}
Public void onJobFinished(){
If(ctx.allJobFinished()){
{///used total time ….}
}
}
最后,增加一個(gè) Job 事件監(jiān)聽(tīng)器,這里是所有 Job 完成后,往客戶端發(fā)送 RowEnd 包,結(jié)束整個(gè)流程。
ctx.setJobEventListener(new JobEventHandler(){public void onJobFinished(){ client.writeRowEndPackage()}});
以上提供一個(gè) SQL 執(zhí)行框架,完全是異步的模式執(zhí)行,并且以后會(huì)提供更多高質(zhì)量的 API,簡(jiǎn)化分布式數(shù)據(jù)處理,比如內(nèi)存結(jié)合文件的數(shù)據(jù) JOIN 算法,分組算法,排序算法等等,期待更多的牛人一起來(lái)完善
6.Spark/Storm 對(duì) join 擴(kuò)展
看到這個(gè)標(biāo)題,可能會(huì)感到很奇怪,Spark 和 Storm 和 Join 有關(guān)系嗎? 有必要用 Spark,storm 嗎?
mycat 后續(xù)的功能會(huì)引入 spark 和 storm 來(lái)做跨分片的 join,大致流程是這樣的在 mycat 調(diào)用 spark,storm的 api,把數(shù)據(jù)傳送到 spark,storm,在 spark,storm 進(jìn)行 join,在把數(shù)據(jù)傳回 mycat,mycat 在返回給客戶端。

本文摘抄于:mycat用戶指南