1. 程式人生 > >PG歸併排序演算法詳解

PG歸併排序演算法詳解

### 前言 - 歸併排序演算法是連線演算法中比較複雜的演算法,相比巢狀迴圈與Hash匹配而言。本節會通過例項來說明該演算法在PG中的具體實現。 - 在PG中,通過狀態機來實現——歸併-連線。當然這裡的完整流程是排序——歸併——連線,由於排序通過Sort操作來完成,這裡就不贅述。 - 這裡的狀態機一共有11中狀態,在這11中狀態的轉換過程中,會根據外表(inner)或內表(inner)讀取到資料的狀態分為—— MJEVAL_MATCHABLE、MJEVAL_NONMATCHABLE、MJEVAL_ENDOFJOIN。可以簡單理解為讀取到非空資料,讀取到資料為空以及所有資料讀取完成。 - 前置條件,內外表均已排序完成且都是升序策略。 ``` -----------初始狀態----------------- EXEC_MJ_INITIALIZE_OUTER EXEC_MJ_INITIALIZE_INNER -----------中間狀態----------------- EXEC_MJ_NEXTOUTER EXEC_MJ_TESTOUTER EXEC_MJ_NEXTINNER EXEC_MJ_SKIP_TEST EXEC_MJ_SKIPOUTER_ADVANCE EXEC_MJ_SKIPINNER_ADVANCE -------------結束狀態----------------- EXEC_MJ_JOINTUPLES EXEC_MJ_ENDOUTER EXEC_MJ_ENDINNER ``` ### 舉例 - 接下來,以一個例項來說明狀態機轉換的具體流程。這裡假設有兩張表進行全外連線——outer/inner。資料如下所示: ``` outer inner 5 5 5 5 6 8 6 8 7 12 8 14 ``` - 簡單提及一下後面說明過程中會涉及到的變數 - 1 (current) outer tuple掃描到的一條外表資料,eg: 5/6/7 - 2 (current) inner tuple掃描到的一條內表資料,eg: 8/12/14 - 3 marked tuple 上一次與outer tuple匹配到的inner tuple,記錄為marked tuple(描述的是inner tuple) - 4 offset tuple 在方法ExecMarkPos執行時,會將當前inner tuple的位置做標記,以便後面inner tuple的重新掃描。通過方法ExecSortRestrPos實現標記。通過方法ExecRestrPos來實現將(current) inner tuple恢復為offset tuple。 - 1 第一輪,初始狀態為EXEC_MJ_INITIALIZE_OUTER,外表遊標下移獲取到outer tuple——5(1)。通過MJEVAL_MATCHABLE切換到狀態EXEC_MJ_INITIALIZE_INNER。 在狀態EXEC_MJ_INITIALIZE_INNER下,內表遊標下移獲取到——5(1)。通過MJEVAL_MATCHABLE切換到EXEC_MJ_SKIP_TEST。 來到狀態EXEC_MJ_SKIP_TEST,這裡執行方法MJCompare,也就是對outer tuple與inner tuple進行了比較,5(1) == 5(1)。標記offset為current,同時將marked tuple置為當前tuple。然後切換到狀態EXEC_MJ_JOINTUPLES。 在狀態EXEC_MJ_JOINTUPLES下,首先更改狀態為EXEC_MJ_NEXTINNER,然後將匹配到的資料返回。 到這裡為止,完成了外表/內表的資料匹配,一輪匹配就結束了。 - 匹配結果 5(1) == 5(1) ``` outer inner outer tuple - 5 5 - marked tuple - offset tuple - inner tuple 5 5 6 8 6 8 7 12 8 14 ``` - 2 第二輪,由於上一輪在退出時將狀態置為EXEC_MJ_NEXTINNER,因此,這一輪從狀態EXEC_MJ_NEXTINNER出發。該狀態下,內表遊標下移獲取資料——5(2),然後呼叫方法MJCompare比較outer tuple與inner tuple,5(1) == 5(2),切換到狀態EXEC_MJ_JOINTUPLES。同樣,切換狀態為EXEC_MJ_NEXTINNER,並且完成該輪掃描(後面不再贅述)。 - 匹配結果 5(1) == 5(2) ``` outer inner outer tuple - 5 5 - marked tuple - offset tuple 5 5 - inner tuple 6 8 6 8 7 12 8 14 ``` - 3 第三輪,繼續來到狀態EXEC_MJ_NEXTINNER,此時內表遊標下移獲取資料——8(1),與outer tuple相比——5(1) < 8(1),切換到狀態EXEC_MJ_NEXTOUTER。 在狀態EXEC_MJ_NEXTOUTER下,外表遊標下移獲取到outer tuple——5(2),同時通過MJEVAL_MATCHABLE切換到EXEC_MJ_TESTOUTER。 在狀態EXEC_MJ_TESTOUTER下,這裡對於**outer tuple與marked tuple**進行了比較,也就是5(2) == 5(1),這裡呼叫方法ExecRestrPos重置了內表的遊標,將inner tuple置為offset tuple,也就是說,下一次內表遊標的下移後獲取的資料是——5(2)。然後切換狀態為EXEC_MJ_JOINTUPLES,同時結束了本輪的匹配。 - 匹配結果 5(2) == 5(1) ``` outer inner 5 5 - marked tuple - offset tuple - inner tuple outer tuple - 5 5 6 8 6 8 7 12 8 14 ``` - 4 第四輪,其實與第二輪的描述基本一致,只是結果發生了變化 - 匹配結果 5(2) == 5(2) ``` outer inner 5 5 - marked tuple - offset tuple outer tuple - 5 5 - inner tuple 6 8 6 8 7 12 8 14 ``` - 5 第五輪,在狀態EXEC_MJ_NEXTINNER下,內表遊標下移獲取資料——8(1),需要注意的是,這裡將**mj_MatchedInner標記為false**。與outer tuple比較——5(2) < 8(1),切換到狀態EXEC_MJ_NEXTOUTER。 在狀態EXEC_MJ_NEXTOUTER下,外表遊標下移獲取到outer tuple——6(1),同時通過MJEVAL_MATCHABLE切換到EXEC_MJ_TESTOUTER。 在狀態EXEC_MJ_TESTOUTER下,這裡對於**outer tuple與marked tuple**進行了比較——6(1) > 5(1),重新載入current inner tuple——8(1),然後切換到狀態EXEC_MJ_SKIP_TEST。 在狀態EXEC_MJ_SKIP_TEST下,呼叫方法MJCompare比較outer tuple與inner tuple——6(1) < 8(1)。切換到狀態EXEC_MJ_SKIPOUTER_ADVANCE。 來到狀態EXEC_MJ_SKIPOUTER_ADVANCE。由於在該輪開始,變數mj_MatchedOuter已經被置為false,因此,這裡並沒有繼續外表的遊標下移,而是修改變數mj_MatchedOuter為true,然後呼叫MJFillOuter將其關聯的inner tuple置為空。這裡沒有進行狀態的切換。該輪迴圈結束。 - 關聯結果 6(1) null ``` outer inner 5 5 - marked tuple - offset tuple 5 5 outer tuple - 6 8 - inner tuple 6 8 7 12 8 14 ``` - 6 由於上面一輪並沒有發生狀態的切換,因此,該輪繼續從狀態EXEC_MJ_SKIPOUTER_ADVANCE出發,只是,上一輪結束時,將變數mj_MatchedOuter置為true,因此,外表遊標下移獲取到outer tuple——6(2),將**mj_MatchedOuter置為false**,然後切換到狀態EXEC_MJ_SKIP_TEST。 來到狀態EXEC_MJ_SKIP_TEST,呼叫方法MJCompare比較outer tuple與inner tuple——6(2) < 8(1)。切換到狀態EXEC_MJ_SKIPOUTER_ADVANCE。與上一輪相似,這裡同樣呼叫MJFillOuter將其關聯的inner tuple置為空。這裡沒有進行狀態的切換。該輪迴圈結束。 - 關聯結果 6(2) null ``` outer inner 5 5 - marked tuple - offset tuple 5 5 6 8 - inner tuple outer tuple - 6 8 7 12 8 14 ``` - 7 這一輪的流程基本與第六輪相似,只是外表遊標下移獲取到outer tuple——7。 - 關聯結果 7 null ``` outer inner 5 5 - marked tuple - offset tuple 5 5 6 8 - inner tuple 6 8 outer tuple - 7 12 8 14 ``` - 8 該輪同樣從狀態EXEC_MJ_SKIPOUTER_ADVANCE出發,外表遊標下移獲取到outer tuple——8,狀態切換到EXEC_MJ_SKIP_TEST。 來到狀態EXEC_MJ_SKIP_TEST,呼叫方法MJCompare比較outer tuple與inner tuple——8 == 8(1)。這裡標記offset為current,同時將marked tuple置為當前tuple。然後切換到狀態EXEC_MJ_JOINTUPLES。同時結束了本輪的匹配。(不要忘記,下一輪從狀態EXEC_MJ_NEXTINNER出發) - 關聯結果 8 == 8(1) ``` outer inner 5 5 5 5 6 8 - marked tuple - offset tuple - inner tuple 6 8 7 12 outer tuple - 8 14 ``` - 9 該輪從狀態EXEC_MJ_NEXTINNER出發,內表遊標下移獲取資料——8(2),呼叫方法MJCompare與outer tuple相比較——8 == 8(2),因此,切換到狀態EXEC_MJ_JOINTUPLES,同時結束本輪的匹配。 - 關聯結果 8 == 8(2) ``` outer inner 5 5 5 5 6 8 - marked tuple - offset tuple 6 8 - inner tuple 7 12 outer tuple - 8 14 ``` - 10 該輪同樣從狀態EXEC_MJ_NEXTINNER出發,內表遊標下移獲取資料——8(2),同時將**變數mj_MatchedInner設定為false**,呼叫方法MJCompare與outer tuple相比較——8 < 12,故切換到狀態EXEC_MJ_NEXTOUTER。 在狀態EXEC_MJ_NEXTOUTER下,外表遊標下移,同時將變數mj_MatchedOuter置為false,由於此時外表已經沒有資料了,因此通過MJEVAL_ENDOFJOIN切換到狀態EXEC_MJ_ENDOUTER。 來到狀態EXEC_MJ_ENDOUTER,由於變數mj_MatchedInner為false,這裡重置mj_MatchedInner為true後,呼叫方法MJFillInner,將inner tuple——12關聯的outer tuple填充為null。這裡沒有狀態切換。該輪迴圈結束。 - 關聯結果 null 12 ``` outer inner 5 5 5 5 6 8 - marked tuple - offset tuple 6 8 7 12 - inner tuple 8 14 outer tuple - eof eof ``` - 11 該輪從上一輪結束的狀態EXEC_MJ_ENDOUTER出發。由於mj_MatchedInner為true,首先呼叫方法ExecMarkPos標記offset為current,然後內表遊標下移,同時將變數變數mj_MatchedInner設定為false,狀態不變。 繼續來到狀態,這裡與繼續呼叫方法MJFillInner,將inner tuple——14關聯的outer tuple填充為null。這裡沒有狀態切換。該輪迴圈結束。 - 關聯結果 null 14 ``` outer inner 5 5 5 5 6 8 - marked tuple 6 8 7 12 - offset tuple 8 14 - inner tuple outer tuple - eof eof ``` - 12 仍然是狀態EXEC_MJ_ENDOUTER,這裡同樣首先呼叫方法ExecMarkPos標記offset為current,然後內表遊標下移,同時將變數變數mj_MatchedInner設定為false。由於此時內表的掃描結束,返回為空,同時該輪迴圈結束。 - 關聯結果 null ``` outer inner 5 5 5 5 6 8 - marked tuple 6 8 7 12 8 14 - offset tuple outer tuple - eof eof - inner tuple ``` - 至此,歸併排序的舉例就結束了。我推薦大家在閱讀本文的時候最好參照pg原始碼——nodeMergejoin.c檔案中的ExecMergeJoin方法,有事半功倍之效。 ### 總結 - 狀態EXEC_MJ_NEXTINNER,該狀態的前置狀態只有EXEC_MJ_JOINTUPLES。在該狀態下內表遊標下移獲取資料,因此,outer tuple與inner tuple比較只有兩種情況 - 1 outer tuple = inner tuple - 2 outer tuple < inner tuple - 狀態EXEC_MJ_TESTOUTER,該狀態的前置狀態只有EXEC_MJ_NEXTINNER與EXEC_MJ_NEXTOUTER。而在該狀態下外表遊標下移獲取資料,因此,outer tuple與inner tuple比較只有兩種情況 - 1 outer tuple = inner tuple - 2 outer tuple > inner tuple - 狀態EXEC_MJ_NEXTOUTER與EXEC_MJ_SKIPOUTER_ADVANCE的區別: 前者會切換到狀態EXEC_MJ_TESTOUTER來比較。 而後者會切換到EXEC_MJ_SKIP_TEST來比較。 - 狀態EXEC_MJ_SKIP_TEST與EXEC_MJ_TESTOUTER區別: 前者outer tuple與inner tuple比較有三種可能的結果。這裡比較的是current outer tuple與current inner tuple。 而後者只有兩種可能的情況。而這裡比較的是current outer tuple與makred inner tuple。 如果current outer tuple > marked inner tuple,那麼inner tuple從current tuple與outer tuple切換到狀態EXEC_MJ_SKIP_TEST進行比較。 - 恢復標記 在狀態EXEC_MJ_TESTOUTER中遇到outer tuple == marked tuple時,這裡會置inner tuple為offset tuple,並且將inner tuple置為之前儲存的offset tuple。 ### 圖示 ![](https://img2020.cnblogs.com/blog/1365945/202003/1365945-20200315142540643-13035736