sql中join的效能是個老大難問題,特別是關聯表較多時,計算效能會急劇下降。
sql實現join一般是採用hash分堆的辦法,即先計算關聯鍵的hash值,再將相同hash值的記錄放到一起再做遍歷對比。每乙個join都要做一輪這樣的運算。
如果資料量相對於記憶體並不是很大,可以事先全部載入到記憶體中,那麼可以利用記憶體指標的機制,事先把關聯關係建立好。這樣做運算時就不必再做hash與對比運算了。具體來說,就是在資料載入時一次性把hash和對比運算做完,用指標方式儲存關聯結果,然後每次運算可以直接引用到關聯記錄,從而提高運算的效能。
不幸的是,sql沒有指標資料型別,無法實現這個優化邏輯,即使資料量可以在記憶體中裝下,也很難利用預關聯技巧提速,基於sql的記憶體資料庫也大都有這個缺點。而spl有指標資料型別,就可以實現這種機制。
我們下面來測試一下sql實現單錶計算和多表關聯計算的差距,再用spl利用預關聯技巧同樣做一遍,看一下兩者的差距對比。
採用tpch標準生成的8張資料表,共50g資料(要小到能放進記憶體)。tpch資料表的結構網上有很多介紹,這裡就不再贅述了。
測試機有兩個intel2670 cpu,主頻2.6g,共16核,記憶體128g,ssd固態硬碟。
由於 lineitem 表資料量太大,這台伺服器記憶體不足以將它裝入,所以建立了一張表結構與它一樣的表 orderdetail, 將資料量減少到記憶體能裝下,下面就用這張表來做測試。
為方便看出差距,下面測試都用單執行緒計算,多核並不起作用。
這裡用 oracle 資料庫作為 sql 測試的代表,從orderdetail表裡查詢每年零件訂單的總收入。
查詢的sql語句如下:
select
l_year,
sum(volume) as revenue
from
select
extract(year from l_shipdate) as l_year,
(l_extendedprice * (1 - l_discount) ) as volume
from
orderdetail,
part
where
p_partkey = l_partkey
and length(p_type)>2
) shipping
group by
l_year
order by
l_year;
查詢的sql語句如下:
select
l_year,
sum(volume) as revenue
from
select
extract(year from l_shipdate) as l_year,
(l_extendedprice * (1 - l_discount) ) as volume
from
supplier,
orderdetail,
orders,
customer,
part,
nation n1,
nation n2
where
s_suppkey = l_suppkey
and p_partkey = l_partkey
and o_orderkey = l_orderkey
and c_custkey = o_custkey
and s_nationkey = n1.n_nationkey
and c_nationkey = n2.n_nationkey
and length(p_type) > 2
and n1.n_name is not null
and n2.n_name is not null
and s_suppkey > 0
) shipping
group by
l_year
order by
l_year;
兩表關聯
六表關聯
執行時間(秒)
26167
兩個查詢語句都用了巢狀寫法,oracle自動優化後的計算效能比無巢狀時還要好一些(無巢狀時group by和select有可能會有重複計算)。
這兩個測試資料是多次執行後的結果,在測試中發現,oracle 在第一次執行某查詢時,往往比第 2、3... 次要慢很多,說明在記憶體大於資料量時,資料庫可以把全部資料都快取進記憶體(oracle的快取很強),所以我們取多次執行中最快那一次的時間,這樣就幾乎沒有硬碟讀取時間,僅是運算時間。
同時,在上面兩組測試中,過濾條件始終都為真,也就是沒有對資料產生實質過濾,兩個查詢都涉及orderdetail表的全部記錄,計算規模是相當的。
從測試結果可以看出,六表關聯比兩表關聯慢了167/26=6.4倍!效能下降非常多。排除掉硬碟時間後,這裡增加的時間主要就是表間關聯以及針對關聯表字段的判斷,而這些判斷非常簡單,所以大部分時間消耗在表間關聯上。
這個測試表明,sql的join效能確實很差。
實現預關聯的spl指令碼如下:
a
1>env(region, file(path+"region.ctx").create().memory().keys@i(r_regionkey))
2>env(nation, file(path+"nation.ctx").create().memory().keys@i(n_nationkey))
3>env(supplier, file(path+"supplier.ctx").create().memory().keys@i(s_suppkey))
4>env(customer, file(path+"customer.ctx").create().memory().keys@i(c_custkey))
5>env(part, file(path+"part.ctx").create().memory().keys@i(p_partkey))
6>env(orders,file(path+"orders.ctx").create().memory().keys@i(o_orderkey))
7>env(orderdetail,file(path+"orderdetail.ctx").create().memory())
8>nation.switch(n_regionkey,region)
9>customer.switch(c_nationkey,nation)
10>supplier.switch(s_nationkey,nation)
11>orders.switch(o_custkey,customer)
12>orderdetail.switch(l_orderkey,orders;l_partkey,part;l_suppkey,supplier)
指令碼中前7行分別將7個組表讀入記憶體,生成內錶,並設成全域性變數。後5行完成表間連線。在spl伺服器啟動時,就先執行此指令碼,完成環境準備。
我們來看看預關聯後,記憶體中表物件的資料結構,以orderdetail為例:
圖中只列了orderdetail的第一條記錄的預關聯情況,其它記錄與此類似。限於版面寬度,各表只列出了部分字段。
編寫spl指令碼如下:
a
1=orderdetail.select(len(l_partkey.p_type)>2)
2=a1.groups(year(l_shipdate):l_year; sum(l_extendedprice * (1 - l_discount)):revenue)
編寫spl指令碼如下:
a
1=orderdetail.select(len(l_partkey.p_type)>2 && l_orderkey.o_custkey.c_nationkey.n_name!=null && l_suppkey.s_nationkey.n_name!=null && l_suppkey.s_suppkey>0 )
2=a1.groups(year(l_shipdate):l_year;
sum(l_extendedprice * (1 - l_discount)):revenue)
預關聯後,spl**也非常簡單,關聯表的字段直接可以作為本表字段的子屬性訪問,很易於理解。
兩表關聯
六表關聯
執行時間(秒)
2856
六表關聯僅僅比兩表關聯慢2倍,基本上就是增加的計算量(引用這些關聯表字段)的時間,而因為有了預關聯,關聯運算本身不再消耗時間。
測試結果彙總:
執行時間(秒)
兩表關聯
六表關聯
效能降低倍數
sql26
1676.4
spl預關聯
2856
2六表關聯比兩表關聯,sql慢了6.4倍,說明sql處理join消耗cpu很大,效能降低明顯。而採用預關聯機制後的spl只慢2倍,多join幾個表不再出現明顯的效能下降。
在進行關聯表較多的查詢時,如果記憶體大到足以將資料全部讀入記憶體(記憶體資料庫的應用場景),使用預關聯技術將極大地提公升計算效能!而關聯式資料庫(包括記憶體資料庫)用sql語言則無法實現這一優化技術。
效能優化技巧 部分預關聯
在 效能優化技巧 預關聯 中,我們測試了將資料表事先全部載入進記憶體並做好關聯後的查詢效能優化問題,但如果記憶體不夠大,不能將維表和事實表全部裝入,那怎麼辦呢?此時,可以將維表預先裝入記憶體,建好索引,實現維表部分的預關聯,省去一半hash計算。我們下面再來測試一下這種場景,這次用資料量最大 記憶體...
效能優化技巧 記憶體關聯計算
關聯動作會嚴重影響效能,spl支援記憶體預關聯,可以加快關聯動作,從而提公升效能。為了理解關聯動作對效能的影響,下面設計一套oracle關聯表,以及無關聯的寬表,並執行同樣的計算。關聯表的結構和關係如下 資料量 聯絡歷史表 百萬條 使用者表 十萬條 開戶網點 一萬條 商表 一萬條 計算目標 求通訊總...
前端效能優化 預載入
1.什麼是預載入 資源預載入是另乙個效能優化技術,我們可以使用該技術來預先告知瀏覽器某些資源可能在將來會被使用到。預載入簡單來說就是將所有所需的資源提前請求載入到本地,這樣後面在需要用到時就直接從快取取資源。2.為什麼要用預載入 在網頁全部載入之前,對一些主要內容進行載入,以提供給使用者更好的體驗,...