性能優化技巧:TopN

TopN是常見的運算,用SQL寫出來是這樣(以Oracle爲例):
       select * from (select * from T order by x desc) where rownum<=N
這個SQL的運算邏輯從其語句上看,要先做排序(Order by),然後再取出前N條。

我們知道,排序是個非常慢的動作,複雜度很高(n*logn),如果涉及數據量大到內存放不下,那還需要進行內外存數據交換,性能還會急劇下降。

而事實上,要計算TopN,我們能設計出不需要全排序的算法,只要保持一個大小爲N的小集合,對數據集遍歷一次,將遍歷過的數據的前N名保存在這個小集合中,遍歷到新一條數據,如果比當前的第N名還大,則插入進去並丟掉現在的第N名,如果比當前的第N名要小,則不做動作。

這樣計算的複雜度要低很多(n*logN,n是總數據條數),而且一般N都不大,可以在內存中放下,無論數據量有多大,都不會涉及內外存交換問題。

但是,SQL無法描述上面這個計算過程,這時,我們只能寄希望於數據庫引擎是不是能自己優化。使用SPL就容易描述上面這個計算過程,從而實現高性能計算。

我們來測試一下看Oracle是不是會做這個優化,即用Oracle實現TopN後和SPL做同樣運算相比。因爲SPL能使用優化算法,如果Oracle的計算時間和SPL差不多,則說明它做了優化,如果差得很遠,則可能是做了全排序。

 

一、      數據準備和環境

用SPL腳本生成數據文件,數據共兩列,第一列id是小於20億的隨機整數,第二列amount是不大於1千萬的隨機實數。數據記錄爲80億行,生成的原始文本文件大小爲169G。利用數據庫提供的數據導入工具將此文件數據導入到Oracle的數據表topn中,同時也用此文件數據生成SPL組表文件topn.ctx。

在一臺Intel服務器上完成測試,2個Intel3014 CPU,主頻1.7G,共12核,內存64G。數據庫表數據及SPL組表文件均存儲在同一塊SSD硬盤上。

我們刻意將數據量設計得比內存大,這樣如果進行排序則會有內外存交換動作,性能下降會非常大,容易被觀測到。

 

二、      常規TopN

取出topn表中amount最大的前100條。

1.        Oracle測試

查詢用的SQL語句是:

select * from (

select  /*+ parallel(4) */

* from topn order by amount desc

) where rownum<=100;

說明:/*+ parallel(4) */ 是Oracle的並行查詢語法,其中4是並行數。

 

2.        SPL測試

編寫SPL腳本執行測試:

bef4400cbc05de35bf684369d9db10ed.gif A
1 =now() /記錄時間
2 =4 /並行數
3 =file("/home/topn/topn.ctx").create() /產生組表對象
4 =A3.cursor@m(id,amount;;A2).groups(;top(100;-amount))
5 =interval@s(A1,now()) /算出執行時間

與SQL不同,SPL把TopN看成是一種聚合運算,和sum/count這類運算一樣,不同的只是TopN將返回一個集合,而sum/count返回的是單值。但它們的計算邏輯是一樣的,都只需要對原數據集遍歷一次,並且不涉及全排序。

A4格中的groups(;top(100;-amount)就是對全集做TopN聚合運算,計算出前100名。

 

3.        結論與分析

常規TopN測試時間見下表:

測試結果(時間單位:秒)

並行數 1 2 4 8 12
Oracle 1922 952 526 308 256
SPL組表 2641 1565 729 371 321

測試表明,Oracle比SPL還要更快一點,而SPL沒有做全排序,這說明Oracle在這種情況會自動優化。

Oracle比SPL快也可以理解,因爲Oracle是用C++開發的,而目前版本的SPL是用java開發的。C++比java計算更快是正常的,而且這次測試讀取了全部的兩列數據,且數據隨機無序,很難壓縮,所以組表的列式存儲也沒有優勢。

 

三、      增加複雜度

對於最基本的TopN,Oracle很聰明,即使寫成了子查詢也會優化。我們下面增加問題的難度,改成分組後在每一組中做TopN。

具體運算設計爲:按照id字段最後一位數字值進行分組,然後查詢出每組中amount最大的前100條記錄。id是個整數,這樣也就只有10個分組,分組本身的計算量並不大,不過要對分組數據做TopN,整體計算複雜度略高了一些。如果沒有全排序,總體運算時間應當比剛纔的情況多,但還在同一數量級範圍內。

1.        Oracle測試

查詢用的SQL語句是:

select * from (

select  /*+ parallel(2) */

       mod(id,10) as gid,amount,

        row_number()over (partition by mod(id,10) order by amount desc) rn

from topn

) where rn <= 100;

SQL無法直接寫出分組後取TopN的運算,只能藉助窗口函數計算序號,語法中還是有排序(order by)的字樣。

 

2.        SPL組表測試

編寫SPL腳本執行測試:

bef4400cbc05de35bf684369d9db10ed.gif A
1 =now() /記錄時間
2 =4 /並行數
3 =file("/home/topn/topn.ctx").create() /產生組表對象
4 =A3.cursor@m(id,amount;;A2).groups@u(id%10:gid;top(100;-amount))
5 =interval@s(A1,now()) /算出執行時間

因爲SPL把TopN看成是聚合計算,所以可以很容易放在分組彙總中,和全量聚合的寫法幾乎是一樣的。

 

3.        結論與分析

測試結果(時間單位:秒)

並行數 1 2 4 8 12
Oracle 41649 19602 9359 4627 3211
SPL組表 4380 2127 1007 465 349

增加難度後,Oracle比之前的簡單情況慢了10倍還多,而且比SPL做同樣運算也慢了近10倍。這說明Oracle在這種情況下很可能做了排序動作,情況複雜化之後,Oracle的優化引擎不起作用了。

而SPL在這兩種情況下的運算時間差別不到2倍,基本上在同一數量級,符合我們之前的分析,算法的優勢得到了充分的體現。


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