性能優化技巧:維表過濾或計算時的關聯

在事實表與維表的關聯查詢時,常常會遇到需要對維表的數據進行過濾或者針對維表做計算的情況,這時可以有兩種處理方式:

1、先做關聯(如果是內存則可以是預關聯),再對關聯後的事實表進行過濾。就象在《性能優化技巧:預關聯》和《性能優化技巧:外鍵序號化》中做的那樣。

2、先對維表進行過濾,再與事實表做關聯。我們知道,建立關聯時需要維表有索引,過濾之後,原先的索引不再可用了,需要重建索引來產生新的索引。

這兩種方式孰優孰劣,不能一概而論,應當和維表與事實表的數據規模對比有關。下面我們通過實驗來探討一下這些性能優化技巧的效果。

 

一、   測試環境

採用TPCH標準生成的8張數據表,共50G數據。TPCH數據表的結構網上有很多介紹,這裏就不再贅述了。

測試機有兩個Intel2670 CPU,主頻2.6G,共16核,內存128G,SSD固態硬盤。

爲方便看出差距,下面測試都用單線程計算,多核不起作用。

 

二、  數據表全內存

所謂全內存,就是預先把要用到的數據表全都加載到內存裏。我們選擇customer作爲維表,共750萬條記錄;用orders作爲事實表,共7500萬條記錄。

查詢時對維表的過濾條件是left(C_NAME,4)!="shen" && C_NATIONKEY>-1 && C_ACCTBAL>bal,求滿足這些條件的訂單總價。其中前兩個條件總是爲真(爲了增加維表過濾的計算量,以增強實驗的對比效果),bal是個參數,用來測試維表過濾後不同數據規模下的效果。

1.   預關聯

我們先看預關聯後的情況,編寫SPL腳本如下:

bef4400cbc05de35bf684369d9db10ed.gif A
1 >customer=file("/home/ctx/customer.ctx").create().memory().keys@i(C_CUSTKEY)
2 >orders=file("/home/ctx/orders.ctx").create().memory()
3 =orders.switch(O_CUSTKEY,customer)
4 =now()
5 =orders.select(left(O_CUSTKEY.C_NAME,4)!="shen"   && O_CUSTKEY.C_NATIONKEY>-1 && O_CUSTKEY.C_ACCTBAL>bal)
6 =A5.sum(O_TOTALPRICE)
7 =interval@s(A4,now())

A1中讀入維表並創建索引,A2中讀入事實表,A3中進行預關聯,這些時間都不計入測試時間,從A4纔開始計時。

2.  重建索引

編寫SPL腳本如下:

bef4400cbc05de35bf684369d9db10ed.gif A
1 >customer=file("/home/ctx/customer.ctx").create().memory().keys@i(C_CUSTKEY)
2 >orders=file("/home/ctx/orders.ctx").create().memory()
3 =now()
4 =customer.select(left(C_NAME,4)!="shen"   && C_NATIONKEY>-1 && C_ACCTBAL>bal).derive@o().keys@i(C_CUSTKEY)
5 =orders.switch@i(O_CUSTKEY,A4)
6 =A5.sum(O_TOTALPRICE)
7 =interval@s(A3,now())

A4中customer過濾後再重建索引,A5中進行關聯。

 

3.   複用索引

SPL支持在過濾後複用已有的索引,只需將上述A4單元格腳本改爲:

=customer.select@i(left(C_NAME,4)!="shen" && C_NATIONKEY>-1 && C_ACCTBAL>bal)

select加選項@i表示複用customer原來的索引。

 

4.   外鍵序號化

預加載數據表時加載序號化處理過的組表customer_xh.ctx和orders_xh.ctx。

編寫SPL腳本如下:

bef4400cbc05de35bf684369d9db10ed.gif A
1 >customer=file("/home/ctx/customer_xh.ctx").create().memory()
2 >orders=file("/home/ctx/orders_xh.ctx").create().memory()
3 =now()
4 =orders.switch@i(O_CUSTKEY,customer:#)
5 =A4.select(left(O_CUSTKEY.C_NAME,4)!="shen"   && O_CUSTKEY.C_NATIONKEY>-1 && O_CUSTKEY.C_ACCTBAL>bal)
6 =A5.sum(O_TOTALPRICE)
7 =interval@s(A3,now())

序號化關聯不需要索引,所以A1中不創建索引。A4中用customer:#表示用O_CUSTKEY的值與customer的行號關聯。

 

5.  序號化後對位序列

預加載數據表時加載序號化處理過的組表customer_xh.ctx和orders_xh.ctx。

編寫SPL腳本如下:

bef4400cbc05de35bf684369d9db10ed.gif A
1 >customer=file("/home/ctx/customer_xh.ctx").create().memory()
2 >orders=file("/home/ctx/orders_xh.ctx").create().memory()
3 =now()
4 =customer.(left(C_NAME,4)!="shen"   && C_NATIONKEY>-1 && C_ACCTBAL>bal)
5 =orders.select(A4(O_CUSTKEY))
6 =A5.sum(O_TOTALPRICE)
7 =interval@s(A3,now())

在A4中用customer.(過濾條件) 算出一個與記錄數等長的、值爲true或false的序列,我們稱之爲對位序列;orders表中的O_CUSTKEY已經序號化處理過,它的值對應於customer的記錄行號,所以在A5就可以用A4(O_CUSTKEY)來判斷orders中此行數據是否滿足過濾條件。

6.   測試結果與分析

實驗獲得測試結果如下(單位:秒):

維表過濾後記錄數 716萬 613萬 477萬 273萬 68萬
預關聯 41 39 38 37 35
重建索引 39 34 29 25 19
複用索引 35 31 27 23 17
外鍵序號化 53 51 49 48 46
對位序列 25 23 21 19 16

這個實驗中,維表數據記錄750萬行,事實表orders數據記錄7500萬行,是維表的10倍。

在預關聯和外鍵序號化測試中,採用的是先關聯後再過濾的處理方式,複雜的過濾計算要在事實表的行上進行,也就是說過濾計算量是直接過濾維表的10倍!所以整個查詢的運行時間是最長的。預關聯與外鍵序號化相比,在查詢時,前者會省去關聯這一步,所以比後者速度快。

在重建索引和複用索引測試中,採用的是先對維表過濾後再與事實表關聯的處理方式,複雜的過濾計算只在維表的行上進行,所以比預關聯和外鍵序號化要快。複用索引與重建索引相比,過濾、關聯、求和的計算量相同,但會在創建索引這一步上節約時間,所以查詢速度也更快。隨着維表過濾後的數據規模越來越小,重建索引的時間也會減少,整體差距就會變小。

在對位序列測試中,過濾計算也是隻在維表的行上進行,計算出對位序列後,只對事實表進行一次過濾,而不用與事實表關聯,不用建索引也不用計算hash值,所以速度是最快的!

 

三、   維表內存、事實表外存

這次我們選擇orders作爲維表,共7500萬條記錄;用lineitem作爲事實表,共3億條記錄。

查詢時對維表的過濾條件是left(O_ORDERPRIORITY,2)!="9-" && O_ORDERSTATUS!="A" && O_ORDERDATE>date("1990-01-01") && O_TOTALPRICE>price,求滿足這些條件的訂單總價。其中前三個條件總是爲真(爲了增加維表過濾的計算量,以增強實驗的對比效果),price是個參數,用來測試維表過濾後不同數據規模下的效果。

 

1.   關聯後再過濾

我們先看關聯後再過濾的情況,編寫SPL腳本如下:

bef4400cbc05de35bf684369d9db10ed.gif A
1 >orders=file("/home/ctx/orders.ctx").create().memory().keys@i(O_ORDERKEY)
2 =now()
3 =file("/home/ctx/lineitem.ctx").create().cursor(L_ORDERKEY,L_EXTENDEDPRICE)
4 =A3.switch@i(L_ORDERKEY,orders)
5 =A4.select(left(L_ORDERKEY.O_ORDERPRIORITY,2)!="9-"   && L_ORDERKEY.O_ORDERSTATUS!="A" &&   L_ORDERKEY.O_ORDERDATE>date("1990-01-01") &&   L_ORDERKEY.O_TOTALPRICE>price)
6 =A5.total(sum(L_EXTENDEDPRICE))
7 =interval@s(A2,now())

A1中讀入維表並創建索引,這不計入測試時間,從A2纔開始計時。

由於事實表很大,使用遊標讀取數據,並與維表關聯後再過濾。

2.   重建索引

編寫SPL腳本如下:

bef4400cbc05de35bf684369d9db10ed.gif A
1 >orders=file("/home/ctx/orders.ctx").create().memory().keys@i(O_ORDERKEY)
2 =now()
3 =orders.select(left(O_ORDERPRIORITY,2)!="9-"   && O_ORDERSTATUS!="A" &&   O_ORDERDATE>date("1990-01-01") && O_TOTALPRICE>price).derive@o().keys@i(O_ORDERKEY)
4 =file("/home/ctx/lineitem.ctx").create().cursor(L_ORDERKEY,L_EXTENDEDPRICE).switch@i(L_ORDERKEY,A3)
5 =A4.total(sum(L_EXTENDEDPRICE))
6 =interval@s(A2,now())

A3中orders過濾後再重建索引。

 

3.   複用索引

只需將上述A3單元格腳本改爲:

=orders.select@i(left(O_ORDERPRIORITY,2)!="9-" && O_ORDERSTATUS!="A" && O_ORDERDATE>date("1990-01-01") && O_TOTALPRICE>price)

select加選項@i表示複用orders原來的索引。

 

4.  外鍵序號化

預加載數據表時加載序號化處理過的組表orders_xh.ctx,且不用創建索引。

編寫SPL腳本如下:

bef4400cbc05de35bf684369d9db10ed.gif A
1 >orders=file("/home/ctx/orders_xh.ctx").create().memory()
2 =now()
3 =file("/home/ctx/lineitem_xh.ctx").create().cursor(L_ORDERKEY,L_EXTENDEDPRICE)
4 =A3.switch@i(L_ORDERKEY,orders:#)
5 =A4.select(left(L_ORDERKEY.O_ORDERPRIORITY,2)!="9-"   && L_ORDERKEY.O_ORDERSTATUS!="A" && L_ORDERKEY.O_ORDERDATE>date("1990-01-01")   && L_ORDERKEY.O_TOTALPRICE>price)
6 =A5.total(sum(L_EXTENDEDPRICE))
7 =interval@s(A2,now())

A4中用orders:#表示用L_ORDERKEY的值與orders的行號關聯。

 

5.  序號化後對位序列

預加載數據表時加載序號化處理過的組表orders_xh.ctx,且不用創建索引。

編寫SPL腳本如下:

bef4400cbc05de35bf684369d9db10ed.gif A
1 >orders=file("/home/ctx/orders_xh.ctx").create().memory()
2 =now()
3 =orders.(left(O_ORDERPRIORITY,2)!="9-"   && O_ORDERSTATUS!="A" &&   O_ORDERDATE>date("1990-01-01") && O_TOTALPRICE>price)
4 =file("/home/ctx/lineitem_xh.ctx").create().cursor(L_ORDERKEY,L_EXTENDEDPRICE).select(A3(L_ORDERKEY))
5 =A4.total(sum(L_EXTENDEDPRICE))
6 =interval@s(A2,now())

查詢實現原理與全內存時相同。

6.  測試結果與分析

實驗獲得測試結果如下(單位:秒):

維表過濾後記錄數 6443萬 4995萬 3590萬 2249萬 428萬
關聯後過濾 101 98 97 94 92
重建索引 102 98 92 73 53
複用索引 85 82 77 74 57
外鍵序號化 79 78 76 75 72
對位序列 53 49 47 43 39

這個實驗中,維表數據記錄7500萬行,事實表lineitem數據記錄3億行,是維表的4倍。

查詢過程的計算原理與上一節分析的相同,但事實表與維表的數據規模對比倍數下降,由10倍變爲了4倍,外鍵序號化與複用索引相比,速度差距不是很明顯了,甚至在維表過濾掉的記錄較少時,因爲序號化關聯比hash值比對關聯更佔優勢,查詢速度還略快。

 

四、   總結

根據前面的測試結果和分析,對於維表有過濾或計算時的查詢,應該採用何種優化技巧來獲得最佳性能,我們作如下總結。

1、事實表數據記錄比維表小

1) 如果數據表能夠全部裝進內存,採用預關聯。

2) 如果不能裝進內存,但對維表和外鍵做了序號化處理,採用先序號化關聯再對事實表過濾。

3) 如果不能裝進內存,又沒有做序號化處理,採用先按外鍵值關聯再對事實表過濾。

 

2、事實表數據記錄遠大於維表

1) 如果數據表做了序號化處理,採用對位序列技術。

2) 如果數據表沒做序號化處理,採用先對維表過濾並複用索引,再按外鍵值關聯查詢。

 

3、事實表數據記錄比維表大得不多

1) 如果數據表做了序號化處理,採用對位序列技術。

2) 如果數據表沒做序號化處理,採用預關聯(能裝進內存的情況下)還是複用索引,建議最好是實測一下。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章