SparkSql 中外串連查詢中的謂詞下推規則,sparksql謂詞
SparkSql
SparkSql是架構在spark計算架構之上的分布式Sql引擎,使用DataFrame和DataSet承載結構化和半結構化資料來實現資料複雜查詢處理,提供的DSL可以直接使用scala語言完成sql查詢,同時也使用thrift server提供服務化的Sql查詢功能。SparkSql提供了Data Source API,使用者通過這套API可以自己開發一套Connector,直接查詢各類資料來源,包括NoSql、RDBMS、搜尋引擎以及HDFS等分布式FS上的檔案等。和SparkSql類似的系統,從Sql和計算架構分離角度看應該就是Hive;從面相的業務類型看有PrestoDB、Impala等(都可以在一定程度上應對即系查詢)。
謂詞下推
所謂謂詞(predicate),英文定義是這樣的:A predicate is a function that returns bool (or something that can be implicitly converted to bool),也就是傳回值是true或者false的函數,使用過scala或者spark的同學都知道有個filter方法,這個高階函數傳入的參數就是一個返回true或者false的函數。如果是在sql語言中,沒有方法,只有運算式,where後邊的運算式起的作用正是過濾的作用,而這部分語句被sql層解析處理後,在資料庫內部正是以謂詞的形式呈現的。
那麼謂詞為什麼要下推呢?說白了,這個問題就是要回答到底誰來完成過濾資料的操作。那麼誰都可以來完成資料過濾呢?我們大致可以把SparkSql中的查詢處理流程做如下的劃分:
SparkSql首先會對輸入的sql語句進行一系列的分析,包括詞法解析(可以理解為搜尋引擎中的分詞這個過程)、文法分析以及語義分析(例如判斷database或者table是否存在、group by必須和彙總函式結合等規則);之後是執行計畫的產生,包括邏輯計劃和物理計劃,其中在邏輯計劃階段會有很多的最佳化,而物理計劃則是RDD的DAG圖的產生;這兩步完成之後則是具體的執行了(也就是各種重量級的計算邏輯),這就會有各種物理操作符(RDD的Transformation)的亂入,和本文討論的問題相關的則是Filter和Scan兩個操作符。其中Scan操作符直接面向底層資料來源,完成資料來源的掃描讀取;Filter操作符完成掃描後資料的過濾。
我們知道,可以通過封裝SparkSql的Data Source API完成各類資料來源的查詢,那麼如果底層資料來源無法高效完成資料的過濾,就會執行直接的全域掃描,把每條相關的資料都交給SparkSql的Filter操作符完成過濾,雖然SparkSql使用的Code Generation技術極大的提高了資料過濾的效率,但是這個過程無法避免大量資料的磁碟讀取,甚至在某些情況下會涉及網路IO(例如資料非本地化時);如果底層資料來源在進行掃描時能非常快速的完成資料的過濾,那麼就會把過濾交給底層資料來源來完成,這就是SparkSql中的謂詞下推(至於哪些資料來源能高效完成資料的過濾以及SparkSql是又如何完成高效資料過濾的則不是本文討論的重點)。
外串連查詢和串連條件
外串連查詢(outter join),分為左外串連查詢、右外串連查詢以及全外串連查詢,全外串連使用的情境不多,所以本文重點討論的是左串連查詢和右串連查詢。
串連條件,則是指當這個條件滿足時兩表的兩行資料才能”join“在一起被返回,例如有如下查詢:
SELECT LT.value, RT.value
FROM lefttable LT LEFT JOIN righttable RT
ON LT.id = RT.id AND LT.id > 1
WHERE RT.id > 2
其中的“LT.id=RT.id AND LT.id>1” 這部分條件被稱為“join中條件”,直接用來判斷被join的兩表的兩行記錄能否被join在一起,如果不滿足這個條件,兩表的這兩行記錄並非全部被踢出局,而是根據串連查詢類型的不同有不同的處理,所以這並非一個單表的過濾過程或者兩個表的的“聯合過濾”過程;而where後的“RT.id>2”這部分被稱為“join後條件”,就是一個單表過濾過程。而上邊提到的謂詞下推能否在兩類條件中使用,在SparkSql中則有特定的規則,以左外串連查詢為例,規則如下:
接下來對這個表格中的規則進行詳細的分析。
假設我們有兩張表,表結構很簡單,資料也都只有兩條,但是足以講清楚我們的下推規則,兩表如下:
lefttable:
rigthtable:
左表join後條件下推
查詢語句如下:
SELECT LT.id, LT.value, RT.value
FROM lefttable LT
LEFT JOIN righttable RT
ON LT.id = RT.id
WHERE LT.id > 1
來分析一下LT.id>1下推到左表進行資料過濾的結果,經過LT.id>1過濾後,左表變為:
此時再和右表進行左串連,左表id為2的行,在右表中能找到id為2的行,則串連結果如下:
可見,條件下推過濾了左表整整50%的資料,相當牛叉,雖然只有兩條。究其原因,是因為在SparkSql中,把以上的查詢解析成了如下的子查詢:
SELECT LT.id, LT.value, RT.value
FROM (SELECT id, value
FROM lefttable LT
WHERE LT.id > 1
) TT
LEFT JOIN righttable RT
ON TT.id = RT.id
這是一個非相互關聯的子查詢,即完全可以先完成子查詢,再完成父查詢,子查詢在查詢過程中和外部查詢沒有關聯關係。
左表join中條件不下推
查詢語句如下:
SELECT LT.id, LT.value, RT.value
FROM lefttable LT LEFT JOIN righttable RT
ON LT.id = RT.id AND LT.id > 1
謂詞下推是為了提高查詢效率,如果不下推也可以得到正確的查詢結果,所以來看看不下推的情況下計算出的正確結果,join過程如下:
第一步:左表id為1的行在右表中能找到相等的id,但是左表的id為1,是不滿足第二個join條件(LT.id>1)的,所以左表這一條相當於沒有和右表join上,所以左表的值value保留,而右表的value為null(你沒滿足join中條件沒join上還把你的值保留,給我搞個空值?沒辦法,就是這麼任性)。
第二步:左表id為2的行在右表中能找到,而且左表id為2的行的id大於1,兩個join條件都滿足,所以算是和右表join上了,所以左表和右表的value都保留。最終的查詢結果如下:
那麼如果把"LT.id>1“這個條件下推到做表,會得到什麼結果呢?
首先左表經過"LT.id>1“過濾後,如下:
此時再和右表串連,左表id為2的行在右表中能找到,且滿足”LT.id = RT.id AND LT.id > 1“這個join中條件,所以兩表的value都被保留。左表中已經沒有資料了,查詢結束,查詢結果如下:
這個查詢結果和不下推的正確結果不一致,顯然是個錯誤的結果,所以左表join中條件是不能下推進行資料過濾的。
右表join中條件下推
查詢語句如下:
SELECT LT.id, LT.value, RT.value
FROM lefttable LT LEFT JOIN righttable RT
ON LT.id = RT.id
AND RT.id > 1
現在把RT.id>1這個右表join中條件下推,來過濾右表,過濾後如下:
然後左表再和右表進行左串連,流程如下:
第一步:左表id為1的行在右表中沒有,此時左表值保留,右表為null
第二步:左表id位2的行在右表中有,並且RT.id大於1,兩個join條件都滿足,則左表和右表的值都保留。查詢結果如下:
那麼如果不下推(為了得到正確結果),來看看結果,流程如下:
第一步:左表id為1的行在右表中有,但是不滿足第二個join條件,所以這行算是沒join上,所以左表資料保留,右表為null
第二步:左表id為2的行在右表中有,也滿足第二個join條件,所以左右表的資料都保留。
可見,右表join中條件下推不下推,結果一樣,所以,幹嗎不下推?可以過濾掉一半的資料呢。Sparksql中的等價處理語句是:
SELECT LT.id, LT.value, RT.value
FROM LT LEFT JOIN (SELECT id, value
FROM righttable RT
WHERE RT.id > 1
) TT
ON LT.id = TT.id
右表join後條件不下推
這個應該是最違反常規理解的查詢了,查詢語句如下:
SELECT LT.id, LT.value
FROM lefttable LEFT JOIN righttable RT
ON LT.id = RT.id
WHERE RT.id > 1
首先來看,join後條件不下推的情況,流程如下:
第一步:左表id為1的行在右表中可以找到,但是此時僅僅滿足join條件,在使用where條件判斷這條串連後資料時,發現右表的id不滿足RT.id>1的條件,所以這條join結果不保留(注意,這裡是不保留,全都不保留,左表右表都不保留,要跟上邊的沒join上,右表的值為null的情況區別開,這也是關鍵所在)
第二步:左表id為2的行和右表id為2的行join上了,同時也滿足RT.id>1的where條件。
很明顯,這是一條符合語義的正確的查詢結果。
好了,接下來看看右表join後條件下推的情況:
第一步:使用RT.id>1過濾右表,過濾後右表只剩一行id為2的行
第二步:左表id為1的行在過濾後的右表中沒有,此時左表值保留,右表值為null
第三步:左表id為2的行在右表中有,此時左表值保留,右表值也保留。
結果如下:
很明顯這其實是一個錯誤的結果。
至此,左聯結查詢的四條規則分析完了,可以看出,在SparkSql中對於外串連查詢時的過濾條件,並不能在所有情況下都用來進行資料來源的過濾,如果使用得當會極大的提升查詢效能,如果使用不當,則會產生錯誤的查詢結果,而這種錯誤結果又不易發覺,所以使用時要格外小心。
推薦閱讀:
1,SparkSql的最佳化器-Catalyst
2,SparkSql的Catalyst之圖解簡易版
3,實戰phoenix
關於Spark學習技巧
kafka,hbase,spark,Flink等入門到深入源碼,spark機器學習,大資料安全,巨量資料營運,請關注浪尖公眾號,看高品質文章。
更多文章,敬請期待
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。