openGauss核心分析(八):執行運算元探究

小侃資料庫發表於2023-03-10

執行引擎位於最佳化器和儲存引擎之間,負責將資料從儲存引擎讀取出來,根據計劃將資料處理加工返回給客戶端。執行器接收到的指令就是最佳化器應對SQL查詢而翻譯出來的關係代數運算子所組成的執行樹,如下圖所示:

微信截圖_20220927152725.png

圖中每一個方塊代表一個具體關係運算代數符,我們稱之為運算元,每個運算元有統一的介面,從下層的一個或者多個運算元獲得輸入,然後將運算結果返回給上層運算元。整個查詢執行過程主要是兩個流,驅動流和資料流。

  • 向上的流代表資料流,是指下層運算元將資料返回給上層運算元的過程,這是一個從下至上、從葉節點到跟節點的過程。在openGauss中,所有的葉子節點都是表資料掃描運算元,這些節點是所有計算的資料來源頭。資料從葉子節點,透過逐層計算,然後從根節點返回給使用者。
  • 向下的流代表控制流,是指上層運算元驅動下層運算元執行的過程,這是一個從上至下、由根節點到葉節點的過程。從程式碼層面來看,即上層運算元會根據需要呼叫下層運算元的函式介面,去獲取下層運算元的輸入。驅動流是從根節點逐層傳遞到葉子節點。

執行器的整體目標就是在每一個由最佳化器構建出來的執行樹上,透過控制流驅動資料流在執行樹上高效的流動,其流動的速度決定了執行器的處理效率。

運算元分類

關聯式資料庫本身是對關係集合Relation的運算操作,執行引擎作為運算的控制邏輯主體也是圍繞著關係運算來實現的,在傳統資料庫實現理論中,運算元的分類可以分成以下幾類:

掃描運算元(Scan Plan Node)

掃描節點負責從底層資料來源抽取資料,資料來源可能是來自檔案系統,也可能來自網路(分散式查詢)。一般而言掃描節點都位於執行樹的葉子節點,作為執行樹PlanTree的資料輸入來源。

關鍵特徵:輸入資料、葉子節點、表示式過濾。

image.png

控制運算元(Control Plan Node)

控制運算元一般不對映代數運算子,通常是為了執行器完成一些特殊的流程引入的運算元。

關鍵特徵:用於控制資料流程。

image.png

物化運算元(Materialize Plan Node)

物化運算元一般指演算法要求,在做運算元邏輯處理的時候,要求把下層的資料進行快取處理,因為對於下層運算元返回的資料量不可提前預知,因此需要在演算法上考慮資料無法全部放置到記憶體的情況。

關鍵特徵:需要掃描所有資料之後才返回。

image.png

連線運算元(Join Plan Node)

這類運算元是為了應對資料庫中最常見的關聯操作。
關鍵特徵:多個輸入。

按照實現方式有3種關聯運算元。

image.png

按照連線型別有6種關聯運算元。

image.png

下面重點分析Seqscan運算元的程式碼流程。

Seqscan 運算元

image.png

ExecInitSeqScan

ExecInitSeqScan函式初始化SeqScan狀態節點,負責節點狀態結構構造。

SeqScanState* ExecInitSeqScan(SeqScan* node, EState* estate, int eflags)
{
    ……    /*
     * create state structure
     */
    SeqScanState* scanstate = makeNode(SeqScanState); // SeqScan狀態節點
 
    scanstate->ps.plan = (Plan*)node;
    scanstate->ps.state = estate;
    scanstate->isPartTbl = node->isPartTbl;
    scanstate->currentSlot = 0;
    scanstate->partScanDirection = node->partScanDirection;
    scanstate->rangeScanInRedis = {false,0,0}; 
    ……    /*
     * tuple table initialization
     */
 
   InitScanRelation(scanstate, estate, eflags); // 初始化掃描表
   ……    /*
     * initialize scan relation
     */
     InitSeqNextMtd(node, scanstate); // 設定獲取元組的函式
     ……
     return scanstate;
}

ExecSeqScan

ExecutePlan函式迴圈呼叫ExecProcNode獲取元組。

static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, bool sendTuples, long numberTuples,
ScanDirection direction, DestReceiver *dest, JitExec::JitContext* motJitContext)
{
    TupleTableSlot *slot = NULL;
    long current_tuple_count = 0; // 初始化
    ……    /*
     * Loop until we've processed the proper number of tuples from the plan.
     */
    for (;;) { // 迴圈呼叫ExecProcNode
    ……
        if (unlikely(recursive_early_stop)) {
            slot = NULL;
        } else if (motJitContext && !IS_PGXC_COORDINATOR && JitExec::IsMotCodegenEnabled()) {
            // MOT LLVM
            int scanEnded = 0;
            if (!motFinishedExecution) {
                // previous iteration has not signaled end of scan
                slot = planstate->ps_ResultTupleSlot;
                uint64_t tuplesProcessed = 0;
                int rc = JitExec::JitExecQuery(
                    motJitContext, estate->es_param_list_info, slot, &tuplesProcessed, &scanEnded);
                if (scanEnded || (tuplesProcessed == 0) || (rc != 0)) {
                    // raise flag so that next round we will bail out (current tuple still must be reported to user)
                    motFinishedExecution = true;
                }
            } else {
                (void)ExecClearTuple(slot);
            }
        } else {
            slot = ExecProcNode(planstate); // 呼叫ExecProcNode
        }
    ……        /*
         * if the tuple is null, then we assume there is nothing more to
         * process so we just end the loop...
         */
        if (TupIsNull(slot)) { // 元組為空即中止迴圈
            if(!is_saved_recursive_union_plan_nodeid) {
                break;
            }
            ExecEarlyFreeBody(planstate);
            break;
        }
    ……
        {
            (*dest->receiveSlot)(slot, dest); // 簡單select語句呼叫printtup函式
        }
    ……        /*
         * check our tuple count.. if we've processed the proper number then
         * quit, else loop again and process more tuples.  Zero numberTuples
         * means no limit.
         */
        current_tuple_count++; // 計數元組數
        if (numberTuples == current_tuple_count) {
            break;
        }
    }
    ……
}

ExecProcNode函式根據nodeTag執行g_execProcFuncTable對應的函式。

TupleTableSlot* ExecProcNode(PlanState* node)
{
    TupleTableSlot* result = NULL;
    ……
    {
        int index = (int)(nodeTag(node))-T_ResultState;
        Assert(index >= 0 && index <= T_StreamState - T_ResultState);
        result = g_execProcFuncTable[index](node);
    }
    ……
    return result;
}
ExecProcFuncType g_execProcFuncTable[] = {
    ExecResultWrap,
    ……
    ExecSeqScanWrap,
    ExecIndexScanWrap,
    ExecIndexOnlyScanWrap,
    ……
};

ExecSeqScanWrap->ExecSeqScan->ExecScan->ExecScanFetch,ExecScanFetch函式回撥SeqNext獲取元組。

static TupleTableSlot* ExecScanFetch(ScanState* node, ExecScanAccessMtd access_mtd, ExecScanRecheckMtd recheck_mtd)
{
    ……    /*
     * Run the node-type-specific access method function to get the next tuple
     */
    return (*access_mtd)(node); // 回撥SeqNext
}

ExecEndSeqScan

ExecEndSeqScan完成清理工作。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70023856/viewspace-2939077/,如需轉載,請註明出處,否則將追究法律責任。

相關文章