在事實表與維表的關聯查詢時,常常會遇到需要對維表的數據進行過濾或者針對維表做計算的情況,這時可以有兩種處理方式:
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腳本如下:
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腳本如下:
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腳本如下:
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腳本如下:
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腳本如下:
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腳本如下:
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腳本如下:
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腳本如下:
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) 如果數據表沒做序號化處理,採用預關聯(能裝進內存的情況下)還是複用索引,建議最好是實測一下。