第33期:JOIN提速 - 外鍵指針化
我們再來看重新定義JOIN后如何能夠提高運算性能,先看外鍵式JOIN的情況。
設(shè)有兩個表:
其中sales表中的productid是指向products表中id字段的外鍵,id是products表的主鍵。
現(xiàn)在我們想計算銷售額有多少(為簡化討論,就不再設(shè)定條件了),用SQL寫出來:
- SELECT SUM(sales.quantity*products.price) FROM sales JOIN products ON sales.productid=products.id
基于笛卡爾積定義的JOIN,原則上只能兩層循環(huán)全遍歷來計算,不過這個計算量實在太大,關(guān)系數(shù)據(jù)庫一般采用HASH分段方法優(yōu)化,即分別計算兩表關(guān)聯(lián)字段的HASH值,將HASH同值記錄拼到一起再做小范圍遍歷。網(wǎng)上有很多文章介紹這個算法,這里就不詳述了。這樣做后的復(fù)雜度能顯著降低,但仍然要做多次HASH值計算和比對。
一
我們再用前述的簡化的JOIN語法寫出這個運算:
- SELECT SUM(quantity*productid.price) FROM sales
而這個寫法其實也就預(yù)示了它還可以有更好的優(yōu)化方案,下面來看看怎樣實現(xiàn)。
二
我們先考慮全內(nèi)存的情況,如果所有數(shù)據(jù)都能夠裝入內(nèi)存,我們可以實現(xiàn)外鍵指針化。
將事實表sales中的外鍵字段productid,轉(zhuǎn)換成指向維表products記錄的指針,即productid的取值就已經(jīng)是某個products表中的記錄,那么就可以直接引用記錄的字段進(jìn)行計算了。
用SQL不方便描述這個運算的細(xì)節(jié)過程了,我們采用過程式語法、并用文件作為數(shù)據(jù)源來說明計算過程:
1. P=file("products.txt").import()
讀入商品信息表P
2. P.index(id)
為P的主鍵id建立索引方便查找
3. S=file("sales.txt").import()
讀入商品銷售記錄S
4. S.switch(productid,P:id)
將S中的productid字段根據(jù)P的主鍵轉(zhuǎn)換成P的記錄
5. S.sum(quantity*productid.price)
計算銷售額。productid字段取值已經(jīng)轉(zhuǎn)換為對象,可以直接引用其price字段
上面算法中,第2步建主鍵索引一般也是用HASH辦法,對id計算HASH值,第4步轉(zhuǎn)換指針還是計算productid的HASH值與P的HASH索引表對比。這樣的話,如果只做一次關(guān)聯(lián)運算,指針化的方案和傳統(tǒng)HASH分段方案的計算量基本上一樣,沒有根本優(yōu)勢。
但不同的是,如果數(shù)據(jù)能在內(nèi)存中放下,這個指針一旦建立起來之后可以復(fù)用,也就是說第2和第4步只要做一次,下次再做關(guān)于這兩個字段的關(guān)聯(lián)運算時就不必再計算HASH值和比對了,性能就能大幅提高。而關(guān)系代數(shù)體系下沒有對象指針這個概念,并且基于笛卡爾積定義的JOIN運算也無法假定外鍵指向記錄的***性,沒辦法使用外鍵指針化的方法,每次關(guān)聯(lián)時都要計算HASH值并比對。
而且,如果事實表中有多個外鍵分別指向多個維表,傳統(tǒng)的HASH分段JOIN方案每次只能解析掉一個,有N個JOIN要執(zhí)行N遍動作,每次關(guān)聯(lián)后都需要保持中間結(jié)果供下一輪使用,計算過程復(fù)雜得多,數(shù)據(jù)也會被遍歷多次。而外鍵指針化方案在面對多個外鍵時,只要對事實表遍歷一次, 沒有中間結(jié)果,計算過程要清晰很多。
還有一點,內(nèi)存本來應(yīng)當(dāng)是很適合并行計算的,但HASH分段JOIN算法卻不容易并行。即使把數(shù)據(jù)分段并行計算HASH值,但要把相同HASH值的記錄歸聚到一起供下一輪比對,就會發(fā)生共享資源沖突的事情,這會把并行計算的優(yōu)勢完全抵消掉。而外鍵式JOIN模型下,關(guān)聯(lián)兩表的地位不對等,明確區(qū)分出維表和事實表后,只要簡單地將事實表分段就可以并行計算。
將HASH分段技術(shù)參照外鍵屬性方案進(jìn)行改造后,也能一定程度地改善多外鍵一次解析和并行能力,有些數(shù)據(jù)庫能在工程層面上實施這種優(yōu)化。不過,這種優(yōu)化在只有兩個表JOIN時問題不大,在有很多表及各種JOIN混在一起時,數(shù)據(jù)庫并不容易識別出應(yīng)當(dāng)把哪個表當(dāng)作事實表去并行遍歷、而把其它表當(dāng)作維表建立HASH索引,這時優(yōu)化并不總是有效的。所以我們經(jīng)常會發(fā)現(xiàn)當(dāng)JOIN的表變多時性能會急劇下降的現(xiàn)象(常常到四五個表時就會發(fā)生,結(jié)果集并無顯著增大)。而從JOIN模型上引入外鍵概念后,將這種JOIN專門處理時,就總能分清事實表和維表,更多的JOIN表只會導(dǎo)致性能的線性下降。
內(nèi)存數(shù)據(jù)庫是當(dāng)前比較火熱的技術(shù),但上述分析表明,采用SQL模型的內(nèi)存數(shù)據(jù)庫在JOIN運算上是很難快起來的!