天天看點

周遊複用

減少外存(硬碟)通路量一直是提高大資料計算性能的永恒話題,我們也讨論過列存、壓縮等直接減少通路量甚至存儲量的手段。除了這些存儲層面的方法外,在算法和計算實作環節,也可以想辦法減少外存的通路量。

周遊是大資料計算中必不可少的環節。有時候,我們會發現在一個計算任務中,會有兩次(或更多)涉及針對同一批資料的周遊動作。如果我們能有辦法讓兩次周遊合并成一次,那麼總的計算量(CPUT的動作)并沒有差别,但硬碟的通路量會減少了一半,這樣計算性能還是能得到提升,對于資料密集型計算的提升效果還相當明顯。

設有簡化的帳目表T的資料結構中如下字段:賬号A、日期D、發生地P,金額M

現在我們想統計賬号a1和a2的餘額,用SQL寫出來是這樣:

SELECT SUM(M) FROM T WHERE A=a1
SELECT SUM(M) FROM T WHERE A=a2
           

這樣兩句計算就會導緻周遊兩次表T,如果表T非常大,計算效率就很低了。

如果我們把這句SQL寫成這樣:

SELECT SUM(CASE WHEN A=a1 THEN M ELSE 0 END),
        SUM(CASE WHEN A=a2 THEN M ELSE 0 END) FROM T
           

一個語句把這兩個統計值都計算出來,句子複雜了不少,資料庫的總計算量也反而略有變大(判斷次數相同,累計次數變多,要多加很多次0),但是表T卻隻要周遊一次就可以了,最後獲得的運算效率卻要高很多。

作為資料庫程式員,要學會這種技巧。

不過,并不是所有運算都可以用CASE WHEN來對付。

我們想分别統計每天的金額合計和每個發生地的金額合計,寫出SQL是:

SELECT D,SUM(M) FROM T GROUP BY D
SELECT P,SUM(M) FROM T GROUP BY P
           

SQL沒有直接提供周遊複用的文法,不同的WHERE還可以用CASE WHEN去繞,但不同的GROUP BY就無法再合并起來了,隻能周遊兩次表T。

理論上,使用資料庫遊标可以做到這一點,定義一個基于SELECT D,P,M FROM T的遊标,一行行取數,然後分别針對D和P去做GROUP BY運算。這個運算用SQL寫起來實在太麻煩了,而且遊标周遊的性能很差,結果不僅繁瑣而且更慢了。

SQL的體系下解決不了這個問題了,我們需要設計新的概念和文法來實作周遊複用。

在遊标機制中引入管道的概念。遊标周遊資料實施某個運算的同時,将資料壓入到一個管道中,而管道上可以再定義另一個運算,這樣,資料在一次周遊時可以同時獲得遊标本身以及附加的管道上的兩個運算結果。上面的的運算寫出來的大體代碼結構如下:

cs = T.cursor()
ch = channel(cs).groups( P; sum(M) )
dg = cs.groups( D; sum(M) )
pg = ch.result()
           

channel(cs)在遊标cs上綁定一個管道ch,并且定義一個針對P的分組運算,然後遊标cs照常周遊并實施針對D的分組運算,周遊完畢後,從管道ch中取了相關結果就可以了。

前面那個不同條件彙總的問題當然也可以用遊标和管道機制寫出來

cs = T.cursor()
ch = channel(cs).select( A==a2 ).sum(M))
m1 = cs.select( A==a1 ).sum(M)
m2 = ch.result()
           

代碼結構都是一樣的。

當然,一個遊标上還可以附加多個管道,比如剛才這兩件事(條件彙總和不同分組)也可以一次周遊做完:

cs = T.cursor()
ch1 = channel(cs).select( A==a2 ).sum(M))
ch2 = channel(cs).groups( P; sum(M) )
ch3 = channel(cs).groups( D; sum(M) )
m1 = cs.select( A==a1 ).sum(M)
m2 = ch1.result()
dg = ch2.result
pg = ch3.result()
           

再舉一個計算中位數的例子。

計算中位數時需要排序,但一般情況下排序運算隻管排序本身,并不管計數,排序完成了甚至還不知道總共有多少資料, 這時候要找中位數,就還得再做一次COUNT周遊資料,浪費時間。如果有管道機制,我們就可以在排序的同時把計數也做完了。

周遊複用

原文釋出時間為:2018-07-17

本文作者:蔣步星

本文來自雲栖社群合作夥伴“

資料蔣堂

”,了解相關資訊可以關注“