日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

TiDB 源码阅读系列文章(十五)Sort Merge Join

發(fā)布時(shí)間:2025/6/15 编程问答 12 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TiDB 源码阅读系列文章(十五)Sort Merge Join 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>

什么是 Sort Merge Join

在開始閱讀源碼之前, 我們來看看什么是 Sort Merge Join (SMJ),定義可以看 wikipedia。簡單說來就是將 Join 的兩個(gè)表,首先根據(jù)連接屬性進(jìn)行排序,然后進(jìn)行一次掃描歸并, 進(jìn)而就可以得出最后的結(jié)果。這個(gè)算法最大的消耗在于對內(nèi)外表數(shù)據(jù)進(jìn)行排序,而當(dāng)連接列為索引列時(shí),我們可以利用索引的有序性避免排序帶來的消耗, 所以通常在查詢優(yōu)化器中,連接列為索引列的情況下可以考慮選擇使用 SMJ。

TiDB Sort Merge Join 實(shí)現(xiàn)

執(zhí)行過程

TiDB 的實(shí)現(xiàn)代碼在 tidb/executor/merge_join.go 中 MergeJoinExec.NextChunk 是這個(gè)算子的入口。下面以 SELECT * FROM A JOIN B ON A.a = B.a 為例,對 SMJ 執(zhí)行過程進(jìn)行簡述,假設(shè)此時(shí)外表為 A,內(nèi)表為 B,join-keys 為 a,A,B 表的 a 列上都有索引:

  • 順序讀取外表 A 直到 join-keys 中出現(xiàn)另外的值,把相同 keys 的行放入數(shù)組 a1,同樣的規(guī)則讀取內(nèi)表 B,把相同 keys 的行放入數(shù)組 a2。如果外表數(shù)據(jù)或者內(nèi)表數(shù)據(jù)讀取結(jié)束,退出。

  • 從 a1 中讀取當(dāng)前第一行數(shù)據(jù),設(shè)為 v1。從 a2 中讀取當(dāng)前第一行數(shù)據(jù),設(shè)為 v2。

  • 根據(jù) join-keys 比較 v1,v2,結(jié)果分為幾種情況:

    • cmpResult > 0, 表示 v1 大于 v2,把當(dāng)前 a2 的數(shù)據(jù)丟棄,從內(nèi)表讀取下一批數(shù)據(jù),讀取方法同 1。重復(fù) 2。
    • cmpResult < 0, 表示 v1 小于 v2,說明外表的 v1 沒有內(nèi)表的值與之相同,把外表數(shù)據(jù)輸出給 resultGenerator(不同的連接類型會(huì)有不同的結(jié)果輸出,例如外連接會(huì)把不匹配的外表數(shù)據(jù)輸出)。
    • cmpResult == 0, 表示 v1 等于 v2。那么遍歷 a1 里面的數(shù)據(jù),跟 a2 的數(shù)據(jù),輸出給 resultGenerator 作一次連接。
  • 回到步驟 1。

  • 下面的圖展示了 SMJ 的過程:

    讀取內(nèi)表 / 外表數(shù)據(jù)

    我們分別通過 fetchNextInnerRows 或者 fetchNextOuterRows 讀取內(nèi)表和外表的數(shù)據(jù)。這兩個(gè)函數(shù)實(shí)現(xiàn)的功能類似,這里只詳述函數(shù) fetchNextInnerRows 的實(shí)現(xiàn)。

    MergeSortExec 算子讀取數(shù)據(jù),是通過迭代器 readerIterator 完成,readerIterator 可以順序讀取數(shù)據(jù)。MergeSortExec 算子維護(hù)兩個(gè) readerIterator:outerIter 和 innerIter,它們在 buildMergeJoin 函數(shù)中被構(gòu)造。

    真正讀取數(shù)據(jù)的操作是在 readerIterator.nextSelectedRow 中完成, 這里會(huì)通過 ri.reader.NextChunk 每次讀取一個(gè) Chunk 的數(shù)據(jù),關(guān)于 Chunk 的相關(guān)內(nèi)容,可以查看我們之前的文章 TiDB 源碼閱讀系列文章(十)Chunk 和執(zhí)行框架簡介 。

    這里值得注意的是,我們通過 expression.VectorizedFilter 對外表數(shù)據(jù)進(jìn)行過濾,返回一個(gè) curSelected 布爾數(shù)組,用于外表的每一行數(shù)據(jù)是否是滿足 filter 過濾條件。以 select * from t1 left outer join t2 on t1.a=100; 為例, 這里的 filter 是 t1.a=100, 對于沒有通過這個(gè)過濾條件的行,我們通過 ri.joinResultGenerator.emitToChunk 函數(shù)發(fā)送給 resultGenerator, 這個(gè) resultGenerator 是一個(gè) interface,具體是否輸出這行數(shù)據(jù),會(huì)由 join 的類型決定,比如外連接則會(huì)輸出,內(nèi)連接則會(huì)忽略。具體關(guān)于 resultGenerator, 可以參考之前的文章:TiDB 源碼閱讀系列文章(九)Hash Join

    rowsWithSameKey 通過 nextSelectedRow 不斷讀取下一行數(shù)據(jù),并通過對每行數(shù)據(jù)的 join-keys 進(jìn)行判斷是不是屬于同一個(gè) join-keys,如果是,會(huì)把相同 join-keys 的行分別放入到 innerChunkRows 和 outerIter4Row 數(shù)組中。然后對其分別建立迭代器 innerIter4Row 和 outerIter4Row。在 SMJ 中的執(zhí)行過程中,會(huì)利用這兩個(gè)迭代器來獲取數(shù)據(jù)進(jìn)行真正的比較得出 join result。

    Merge-Join

    實(shí)現(xiàn) Merge-Join 邏輯的代碼在函數(shù) MergeJoinExec.joinToChunk, 對內(nèi)外表迭代器的當(dāng)前數(shù)據(jù)根據(jù)各自的 join-keys 作對比,有如下幾個(gè)結(jié)果:

    • cmpResult > 0,代表外表當(dāng)前數(shù)據(jù)大于內(nèi)表數(shù)據(jù),那么通過 fetchNextInnerRows 直接讀取下一個(gè)內(nèi)表數(shù)據(jù),然后重新比較即可。

    • cmpResult < 0,代表外表當(dāng)前數(shù)據(jù)小于內(nèi)表數(shù)據(jù),這個(gè)時(shí)候就分幾種情況了,如果是外連接,那么需要輸出外表數(shù)據(jù) + NULL,如果是內(nèi)連接,那么這個(gè)外表數(shù)據(jù)就被忽略,對于這個(gè)不同邏輯的處理,統(tǒng)一由 e.resultGenerator 來控制,我們只需要把外表數(shù)據(jù)通過 e.resultGenerator.emitToChunk 調(diào)用它即可。然后通過 fetchNextOuterRows 讀取下一個(gè)外表數(shù)據(jù),重新比較。

    • cmpResult == 0,代表外表當(dāng)前數(shù)據(jù)等于內(nèi)表當(dāng)前數(shù)據(jù),這個(gè)時(shí)候就把外表數(shù)據(jù)跟內(nèi)表當(dāng)前數(shù)據(jù)做一次連接,通過 e.resultGenerator.emitToChunk 生成結(jié)果。之后外表跟內(nèi)表分別獲取下一個(gè)數(shù)據(jù),重新開始比較。

    重復(fù)上面的過程,直到外表或者內(nèi)表數(shù)據(jù)被遍歷完,退出 Merge-Join 的過程。

    更多

    我們上面的分析代碼基于 Source-code 分支,可能大家已經(jīng)發(fā)現(xiàn)了一些問題,比如我們會(huì)一次性讀取內(nèi)外表的 Join group(相同的 key)。這里如果相同的 key 比較多,是有內(nèi)存 OOM 的風(fēng)險(xiǎn)的。針對這個(gè)問題,我們在最新的 master 分支做了幾個(gè)事情來優(yōu)化:

  • 外表其實(shí)不需要把相同的 keys 一次性都讀取上來, 它只需要按次迭代外表數(shù)據(jù),再跟內(nèi)表逐一對比作連接即可。這里至少可以減少外表發(fā)生 OOM 的問題,可以大大減少 OOM 的概率。

  • 對于內(nèi)表,我們對 OOM 也不是沒有辦法,我們用 memory.Tracker 這個(gè)內(nèi)存追蹤器來記錄當(dāng)前內(nèi)表已經(jīng)使用的中間結(jié)果的內(nèi)存大小,如果它超過我們設(shè)置的閾值,我們會(huì)采取輸出日志或者終止 SQL 繼續(xù)運(yùn)行的方法來規(guī)避 OOM 的發(fā)生。關(guān)于 memory.Tracker 我們不在此展開,可以留意我們后續(xù)的源碼分析文章。

  • 后續(xù)我們還會(huì)在 Merge-Join 方面做一些優(yōu)化, 比如我們可以做多路歸并,中間結(jié)果存外存等等,敬請期待。

    作者:姚維

    轉(zhuǎn)載于:https://my.oschina.net/zhaiyuan/blog/1924293

    總結(jié)

    以上是生活随笔為你收集整理的TiDB 源码阅读系列文章(十五)Sort Merge Join的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。