溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

優(yōu)化Join運算的系列方法(2)

發(fā)布時間:2020-07-31 01:50:01 來源:網(wǎng)絡(luò) 閱讀:137 作者:raqsoft 欄目:大數(shù)據(jù)

3 半內(nèi)存時的外鍵表

外鍵指針化的前提是事實表和維表都可以裝入內(nèi)存,但實際業(yè)務(wù)中涉及的數(shù)據(jù)量可能很大,那就不能采用這種方法了。

3.1 維表內(nèi)存化

如果只是事實表很大,而維表仍然可以全部裝入內(nèi)存,那么仍然可以采用上面的外鍵指針化方法處理,只要修改一下對事實表的訪問,使用游標的方式取從集文件里分批取數(shù)進行處理即可。不過因為這種指針是在游標取數(shù)時才臨時建立的,所以就不象全內(nèi)存時那樣可以復用已經(jīng)建立過的指針了。

我們?nèi)匀话凑沼脩艏墑e和賣家信用等級匯總訂單數(shù)量,而訂單表太大無法導入內(nèi)存,那么用集算器實現(xiàn)如下:


A

1

=file("訂單表")

2

=A1.cursor@b()

3

=file("用戶信息表").import@b().keys(用戶編號).index()

4

=file("賣家信息表").import@b().keys(賣家編號).index()

5

=A2.switch(用戶編號,A3:用戶編號;賣家編號,A4:賣家編號)

6

=A5.groups(用戶編號.VIP級別:用戶級別,賣家編號.信用等級:賣家等級;sum(訂單編號):訂單數(shù))

這個實現(xiàn)跟外鍵指針化的實現(xiàn)原理相同,只不過訂單表的數(shù)據(jù)沒有一次性導入內(nèi)存,而是通過游標的方式訪問。由于事實表會不斷增長,所以事實表很大而維表較小會是實際業(yè)務(wù)中常見的情況。

這是個多外鍵的例子。多層外鍵的情況和單層外鍵類似,只是在內(nèi)存化某外鍵表時,該表的外鍵表也必須內(nèi)存化,從而可以事先建立內(nèi)存的外鍵指針。臨時基于游標建立的外鍵關(guān)聯(lián)只會針對最下層的外鍵表。

游標也可以實現(xiàn)并行計算,上面的代碼只要改成這樣:


A

1

=file("訂單表").cursor@bm(;4)

2

=file("用戶信息表").import@b().keys(用戶編號).index()

3

=file("賣家信息表").import@b().keys(賣家編號).index()

4

=A1.switch(用戶編號,A2:用戶編號;賣家編號,A3:賣家編號)

5

=A4.groups(用戶編號.VIP級別:用戶級別,賣家編號.信用等級:賣家等級;sum(訂單編號):訂單數(shù))

把來自集文件的訂單表數(shù)據(jù)分成4段游標取出,在執(zhí)行g(shù)roups函數(shù)就會以并行的方式進行計算了。這里之所以可以進行分段取數(shù),是因為數(shù)據(jù)已經(jīng)導出到集文件中了,如果數(shù)據(jù)仍然在數(shù)據(jù)庫中則無法做到這一點的,這也是我們?yōu)槭裁匆褦?shù)據(jù)導出到集文件的原因之一。

如果維表太大也無法裝入內(nèi)存怎么辦?這種情況就要使用集群或者優(yōu)化過的外存HASH JOIN技術(shù)了,后面的篇章中我們會詳細講解。

 

3.2 外鍵序號化

外鍵序號化的思路是,如果維表的主鍵是從1開始的自然數(shù),那么就可以用序號直接定位維表記錄,而不再需要計算和比對HASH值了。這可以看做是在外存實現(xiàn)了外鍵指針化,從而進一步提升性能。按照外鍵序號化思路,前面訂單表和用戶表的關(guān)聯(lián)處理可以改成這樣:


A

1

=file("用戶信息表").import@b()

2

=file("訂單表").cursor@b()

3

=A2.switch(用戶編號,A1:#)

4

=A3.new(訂單編號, 用戶編號.用戶名:用戶名, 用戶編號.VIP級別:用戶級別, 下單時間)

A1,將客戶表全部導入內(nèi)存;

A2,將訂單表使用游標導入;

A3,在A2訂單表中把用戶編號的值作為序號,用這個序號去用戶信息表找相應的記錄,建立關(guān)聯(lián);

A4,通過外鍵屬性化的方式,將外鍵表字段作為用戶名、用戶級別屬性使用。

3.3 序號化準備

但維表的主鍵不一定是序號值,那么就無法直接使用外鍵序號化進行性能優(yōu)化。這時,可以把維表的主鍵轉(zhuǎn)換成序號后再使用外鍵序號化。處理的步驟是這樣的:

1)新建一個鍵值-序號對應表,保存維表的鍵值和自然序號的對應關(guān)系;

2)把維表的鍵值替換為自然序號,得到一個新的維表文件;

3)把事實表里的外鍵值修改為序號,修改的依據(jù)是鍵值-序號對應表,修改后得到一個新的事實表;

這樣就得到了新的維表和事實表文件,舊的表文件也可以刪除了。

 

如果維表增加了新數(shù)據(jù),那么就按照如下步驟處理:

1)先追加鍵值-序號對應表;

2)再把新數(shù)據(jù)追加到新的維表,追加時依據(jù)鍵值-序號對應表;

3)最后追加事實表,追加時依據(jù)鍵值-序號對應表;

當完成了外鍵的序號化以后就可以使用外鍵序號化的方式來提高性能了。序號化這種方法適用于維表基本不變的情況,事實表數(shù)據(jù)則可以不斷追加。

下面仍以訂單表、用戶信息表為例來說明一個序號化的具體實現(xiàn):

1)新建一個用戶信息表的鍵值-序號對應表,保存到集文件中,同時生成一個用戶信息表文件;


A

1

=db.query("select *,0 AS NEW_ID from 用戶信息表 order by 用戶編號")

2

=A1.run(#:NEW_ID)

3

=file("OldKey_NewID").export@b(A2,   用戶編號, NEW_ID)

4

=file("用戶信息表").export@b(A2, NEW_ID:用戶編號, 用戶名,聯(lián)系手機,VIP級別)

A1從數(shù)據(jù)庫的用戶信息表取出所有字段,并增加一個用來保存序號的字段NEW_ID;

A2將NEW_ID賦值為從1開始的自然數(shù);

A3是保存舊的用戶編號和序號到集文件;

A4用NEW_ID字段值作為用戶編號字段的值,其它字段不改變,把數(shù)據(jù)保存到用戶信息表文件。

2)根據(jù)訂單表,得到新的訂單表;


A

1

=file("OldKey_NewID").import@b()

2

=db.cursor("select * from 訂單表")

3

=A2.switch(用戶編號,A1:用戶編號)

4

=A3.run( 用戶編號.NEW_ID:用戶編號)

5

=file("新訂單表").export@ba(A4)

A1把對應關(guān)系表導入內(nèi)存;

A2用游標從訂單表取出數(shù)據(jù);

A3把訂單表里的用戶編號字段根據(jù)對應表進行替換;

A4把替換后的用戶編號字段的值做一個轉(zhuǎn)換(A3得到的用戶編號字段值是記錄類型,所以在A4轉(zhuǎn)變?yōu)樽侄危?/p>

A5把游標數(shù)據(jù)導出到新訂單表文件里(實際中可能要分多次導出);

 

通過這兩步,就可以完成對數(shù)據(jù)庫里已有數(shù)據(jù)的序號化,并導出到用戶信息表、訂單表這兩個集文件,同時還得到了一個鍵值-序號對應表文件,命名為OldKey_NewID。

 

前面提到過,序號化適用于維表數(shù)據(jù)基本不變的情況,如果維表變化了,那就需要重造這些數(shù)據(jù)后再使用序號化。不過,如果能夠明確知道事實表和維表上新追加的數(shù)據(jù)(例如通過時間等條件),那么也可以用下面的辦法來實現(xiàn)。

1)先追加用戶信息表和鍵值-序號對應文件;


A

1

=db.query("select *,0 AS NEW_ID from 用戶信息表 where 注冊時間>’2018-01-01’ order by 用戶編號")

2

=file("用戶信息表").cursor@b().skip()

3

=A1.run(A2+#:NEW_ID)

4

=file("OldKey_NewID").export@ab(A3,   用戶編號, NEW_ID)

5

=file("用戶信息表").export@ab(A3, NEW_ID:用戶編號, 用戶名,聯(lián)系手機,VIP級別)

A1得到用戶信息表要追加的新數(shù)據(jù),這里是從數(shù)據(jù)庫里取2018年以來新注冊的用戶數(shù)據(jù);

A2得到用戶信息表已有記錄條數(shù);

A3填寫新數(shù)據(jù)里的NEW_ID值,從A2開始繼續(xù)計數(shù);

A4把用戶編號和序號追加到鍵值-序號對應的文件;

A5追加新數(shù)據(jù)到用戶信息表文件。

3)追加訂單表;


A

1

=db.query("select * from訂單表 where 下單時間>=’2018-01-01’ order by 訂單編號")

2

=file("OldKey_NewID").cursor@b()

3

=A1.switch(用戶編號,A2:用戶編號)

4

=A3.run( 用戶編號.NEW_ID:用戶編號)

5

=file("訂單表").export@ba(A4)

A1得到訂單表要追加的新數(shù)據(jù)的游標,這里是從數(shù)據(jù)庫取出2018年以來的訂單作為新數(shù)據(jù);

A2是得到鍵值序號的對應表;

A3把新數(shù)據(jù)游標里的用戶編號字段根據(jù)對應表進行替換;

A4把替換后的用戶編號字段的值做一個轉(zhuǎn)換;

A5使用循環(huán)方式從游標取數(shù),追加到訂單表文件,這個過程和用戶信息表的追加是類似的。

上面是一個單外鍵做序號化的例子,對多外鍵的序號化處理也是一樣的,只是有多個維表要處理。如果是多層外鍵,那么上層的就沒有必要做序號化了,只要對最下層的維表做個序號化就可以了,因為上層已經(jīng)全內(nèi)存指針化了。

外鍵序號化處理本質(zhì)是優(yōu)化了查找外鍵的方法,把外鍵值作為序號直接去維表找記錄,所以經(jīng)過外鍵序號化的數(shù)據(jù)仍然可以使用并行計算,實現(xiàn)方式跟前面講的一樣,在此不再詳述。

 

4 同維表和主子表

在這里我們把同維表和主子表兩種情況一起來分析,因為這兩種情況的提速手段是一樣的,那就是有序歸并。

4.1 有序歸并

我們先看簡單的情況,如果兩個表對關(guān)聯(lián)鍵都已經(jīng)是有序的,那么就可以直接使用歸并算法來處理關(guān)聯(lián)。來看一個例子,

訂單表

訂單編號

用戶編號

賣家編號

下單日期

 

訂單明細表

訂單編號

商品編號

數(shù)量

金額

 

賣家信息表

賣家編號

名稱

……

 

用戶信息表

用戶編號

用戶名

……

 

此時訂單表是主表,訂單明細表是子表,這是一個典型的一對多的情況,現(xiàn)在要查詢訂單及其明細,那么就要把兩個表按照訂單編號字段進行關(guān)聯(lián)。先來看一下數(shù)據(jù)量不大時的例子,計算目標是匯總每個賣家的銷售額:


A

1

=file("訂單表").import@b()

2

=file("訂單明細表").import@b()

3

=join@m(A1:訂單,訂單編號;A2:明細,訂單編號)

4

=A3.groups(訂單.賣家編號 :賣家編號; sum(明細.金額):總銷售額 )

A1將訂單表全部導入內(nèi)存。

A2將訂單明細表全部導入內(nèi)存。

A3通過有序歸并算法(@m選項)對兩個表按照訂單編號關(guān)聯(lián)。

A4對join的結(jié)果進行分組匯總。

集算器的join操作的結(jié)果與SQL不同,SQL里join的結(jié)果是兩個表的字段,而集算器join的結(jié)果是把兩個表的記錄作為結(jié)果字段,所以做groups時的語法需要寫成“字段.子字段”這樣(類似“對象.屬性”),例如訪問賣家編號就要寫成“訂單.賣家編號”。

如果數(shù)據(jù)很大無法導入內(nèi)存,則可以使用游標方式進行有序歸并。


A

1

=file("訂單表").cursor@b()

2

=file("訂單明細表"). cursor@b()

3

=joinx(A1:訂單,訂單編號;A2:明細,訂單編號)

4

=A3.groups(訂單.賣家編號:賣家編號; sum(明細.金額):總銷售額 )

注意,這里進行有序歸并的前提是訂單表、訂單明細表已經(jīng)是對訂單編號字段有序的。

A1將訂單表通過游標導入;

A2將訂單明細表通過游標導入;

A3通過有序歸并算法對兩個游標按照訂單編號關(guān)聯(lián);

A4對joinx的結(jié)果進行分組匯總。同樣地,joinx的結(jié)果的字段也是記錄,所以在groups時對賣家編號的訪問語法就變成了訂單.賣家編號,對金額的訪問語法就成了明細.金額。

 

有序歸并還可以和游標外鍵一起使用,例如我們要計算消費總金額大于1000的用戶名:


A

1

=file("訂單表").cursor@b()

2

=file("訂單明細表"). cursor@b()

3

=file("用戶信息表"). import@b()

4

=A1.switch@i(用戶編號, A3:用戶編號)

5

=joinx(A4:訂單,訂單編號;A2:明細,訂單編號)

6

=A5.groups(訂單.用戶編號.用戶名; sum(明細.金額):總額) .select(總額>1000)

A1將訂單表通過游標導入;

A2將訂單明細表通過游標導入;

A3將用戶信息表導入內(nèi)存;

A4使用用戶編號字段和用戶信息表做外鍵關(guān)聯(lián);

A5通過有序歸并算法對兩個游標按照訂單編號關(guān)聯(lián);

A6 通過用戶名字段(訂單.用戶編號.用戶名)進行分組匯總,并選出總額大于1000的。

 

4.2 有序歸并的數(shù)據(jù)準備

不過,如果數(shù)據(jù)事先沒有按主鍵有序呢?那么就需要事先進行排序。同維表和主子表可以在數(shù)據(jù)準備階段就做好排序,這是因為對于同維表或主子表的關(guān)聯(lián),用到的字段都是那一個(一組),即主鍵(的部分);而對于外鍵表,事實表有可能要跟多個維表做關(guān)聯(lián),每次關(guān)聯(lián)的字段都可能是不同的,而一個表是不可能同時對所有的外鍵都有序的。

因此,對于數(shù)據(jù)庫中并不保證次序的原始數(shù)據(jù),我們可以在做數(shù)據(jù)外置時同時進行排序。本節(jié)將描述如何排序以及排序后如何有序地更新數(shù)據(jù)。

先看原始數(shù)據(jù)的導出。如果要排序的同維表或主子表的數(shù)據(jù)源都是數(shù)據(jù)庫,那么就用數(shù)據(jù)庫排序。如果數(shù)據(jù)源不是數(shù)據(jù)庫,那么可以使用集算器的sortx函數(shù)進行排序。排序后用export函數(shù)保存到一個新的文件里。如果要采用分段并行,還要注意在導出的時候加上選項@z。處理流程是這樣的:


A

1

=db.query("select * from 訂單表 order by 訂單編號").cursor()

2

=file("訂單表").export@z(A1;訂單編號)

3

=db.query("select * from 訂單明細表order by 訂單編號").cursor()

4

=file("訂單明細").export@z(A1;訂單編號)

A1,從數(shù)據(jù)庫將訂單表通過游標導入,并且排序;

A2,將排序后的游標數(shù)據(jù)寫入集文件;

A3、A4同樣將數(shù)據(jù)庫的訂單明細表排序后寫入集文件。

 

再來看看如果這兩個表又追加了新數(shù)據(jù)時該怎么處理,我們僅以訂單表的追加為例:


A

1

=file("訂單表"). cursor @b()

2

=db.query("select * from 訂單表 where 下單日期>=’2018-01-01’ order by 訂單編號").cursor()

3

=[A1,A2].mergex(訂單編號)

4

=file("新訂單表").export@z(A1;訂單編號)

A1,將訂單表通過游標導入;

A2,從數(shù)據(jù)庫中將2018年以來產(chǎn)生的新數(shù)據(jù)取出;

A3,兩個游標按照訂單編號字段進行有序歸并;

A4,將歸并后的游標數(shù)據(jù)寫入新的文件。

后續(xù)使用時用新的文件替換舊的訂單表文件,這樣就完成了新增數(shù)據(jù)和歷史數(shù)據(jù)的有序歸并,就可以按照有序的情況進行處理了。

新增數(shù)據(jù)和歷史數(shù)據(jù)的混合,是個有序歸并的過程,并不需要全部重新排序,只是把數(shù)據(jù)再讀寫一遍,時間成本并不高。

 

4.3 并行計算

如果數(shù)據(jù)量確實特別大,頻繁重寫的成本太高,這時可以每隔一個相對合適的周期才重寫所有數(shù)據(jù),未到周期點時先把數(shù)據(jù)保存到一個較小文件,到了周期節(jié)點再把小文件和歷史全文件做歸并,具體的周期根據(jù)實際業(yè)務(wù)來設(shè)定。這樣就會有兩個文件:歷史全文件和周期內(nèi)小文件??梢允褂枚嗦酚螛藖硪黄鹪L問這兩個文件。

例如,可以計劃每隔一個月才重寫所有數(shù)據(jù),每天追加的數(shù)據(jù)合在一個當月的小文件中,在月中只用這個小文件和當日數(shù)據(jù)歸并,到了月末才把當月文件和歷史全文件全部歸并,這樣就能夠減少全量歸并的次數(shù),減少總的處理時間。這種方式下兩個文件就是歷史文件和當月文件。

當然,還可以保留以前每個月的文件,作為歷史數(shù)據(jù)不再改動,然后使用多路游標來訪問這多套數(shù)據(jù),這樣性能可能會更好。這是以日期為例的情況,還可以根據(jù)其它的字段來進行分段方案的設(shè)計,比如按地區(qū)等。

下面用每個月保留一個文件的方法來舉例說明,先實現(xiàn)對當日新產(chǎn)生的數(shù)據(jù)的處理,仍然以訂單表為例:


A

1

=file("訂單表8月").import@b().cursor()

2

=db.query("select * from 訂單表 where 下單日期>=’2018-08-XX’ order by 訂單編號").cursor()

3

=[A1,A2].mergex(訂單編號)

4

=file("新訂單表8月").export@z(A1;訂單編號)

A1,將8月份的訂單表月文件通過游標導入;

A2,從數(shù)據(jù)庫中將2018年8月某一天以來產(chǎn)生的新數(shù)據(jù)取出;

A3,兩個游標按照訂單編號字段進行有序歸并;

A4,將歸并后的游標數(shù)據(jù)寫入新的8月份的文件。

處理后得到每個月份的訂單表集文件,同理也可以得到每個月份的訂單明細表的集文件。每個月份的兩個集文件(訂單和明細)都是根據(jù)訂單時間產(chǎn)生的,對應的主子表記錄(訂單及其對應訂單明細)都在同一月份的文件中,這樣就可以并行地針對每個月的數(shù)據(jù)做有序歸并來實現(xiàn)主子表連接,進一步提速。仍以統(tǒng)計賣家銷售總額為例,下面是具體實現(xiàn):


A

1

=12.(file("訂單表"/~/"月").cursor@b())

2

=12.(file("訂單明細表"/~/"月").cursor@b())

3

=12.(joinx(A1(#):訂單,訂單編號;A2(#):明細 ,訂單編號))

4

=A3.mcursor()

5

=A4.groups(訂單.賣家編號 :賣家編號; sum(明細.金額):總銷售額 )

A1,創(chuàng)建12個月份的訂單表游標;

A2,創(chuàng)建12個月份的訂單明細表游標;

A3,使用joinx對12個月份數(shù)據(jù)進行歸并,得到游標;

A4,合并為多路游標;

A5,對多路游標進行分組匯總。


向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI