PostgreSQL 原始碼解讀(87)- 查詢語句#72(PortalRunSelect->E...
本節介紹了PortalRunSelect->ExecutorRun->ExecutePlan函式以及ExecProcNode的其中一個Real函式(ExecSeqScan)。ExecutePlan函式處理查詢計劃,直到檢索到指定數量(引數numbertuple)的元組,並沿著指定的方向掃描。ExecSeqScan函式順序掃描relation,返回下一個符合條件的元組。
一、資料結構
Plan
所有計劃節點透過將Plan結構作為第一個欄位從Plan結構“派生”。這確保了在將節點轉換為計劃節點時,一切都能正常工作。(在執行器中以通用方式傳遞時,節點指標經常被轉換為Plan *)
/* ----------------
* Plan node
*
* All plan nodes "derive" from the Plan structure by having the
* Plan structure as the first field. This ensures that everything works
* when nodes are cast to Plan's. (node pointers are frequently cast to Plan*
* when passed around generically in the executor)
* 所有計劃節點透過將Plan結構作為第一個欄位從Plan結構“派生”。
* 這確保了在將節點轉換為計劃節點時,一切都能正常工作。
* (在執行器中以通用方式傳遞時,節點指標經常被轉換為Plan *)
*
* We never actually instantiate any Plan nodes; this is just the common
* abstract superclass for all Plan-type nodes.
* 從未例項化任何Plan節點;這只是所有Plan-type節點的通用抽象超類。
* ----------------
*/
typedef struct Plan
{
NodeTag type;//節點型別
/*
* 成本估算資訊;estimated execution costs for plan (see costsize.c for more info)
*/
Cost startup_cost; /* 啟動成本;cost expended before fetching any tuples */
Cost total_cost; /* 總成本;total cost (assuming all tuples fetched) */
/*
* 最佳化器估算資訊;planner's estimate of result size of this plan step
*/
double plan_rows; /* 行數;number of rows plan is expected to emit */
int plan_width; /* 平均行大小(Byte為單位);average row width in bytes */
/*
* 並行執行相關的資訊;information needed for parallel query
*/
bool parallel_aware; /* 是否參與並行執行邏輯?engage parallel-aware logic? */
bool parallel_safe; /* 是否並行安全;OK to use as part of parallel plan? */
/*
* Plan型別節點通用的資訊.Common structural data for all Plan types.
*/
int plan_node_id; /* unique across entire final plan tree */
List *targetlist; /* target list to be computed at this node */
List *qual; /* implicitly-ANDed qual conditions */
struct Plan *lefttree; /* input plan tree(s) */
struct Plan *righttree;
List *initPlan; /* Init Plan nodes (un-correlated expr
* subselects) */
/*
* Information for management of parameter-change-driven rescanning
* parameter-change-driven重掃描的管理資訊.
*
* extParam includes the paramIDs of all external PARAM_EXEC params
* affecting this plan node or its children. setParam params from the
* node's initPlans are not included, but their extParams are.
*
* allParam includes all the extParam paramIDs, plus the IDs of local
* params that affect the node (i.e., the setParams of its initplans).
* These are _all_ the PARAM_EXEC params that affect this node.
*/
Bitmapset *extParam;
Bitmapset *allParam;
} Plan;
二、原始碼解讀
ExecutePlan
PortalRunSelect->ExecutorRun->ExecutePlan函式處理查詢計劃,直到檢索到指定數量(引數numbertuple)的元組,並沿著指定的方向掃描.
/* ----------------------------------------------------------------
* ExecutePlan
*
* Processes the query plan until we have retrieved 'numberTuples' tuples,
* moving in the specified direction.
* 處理查詢計劃,直到檢索到指定數量(引數numbertuple)的元組,並沿著指定的方向移動。
*
* Runs to completion if numberTuples is 0
* 如引數numbertuple為0,則執行至結束為止
*
* Note: the ctid attribute is a 'junk' attribute that is removed before the
* user can see it
* 注意:ctid屬性是"junk"屬性,在返回給使用者前會移除
* ----------------------------------------------------------------
*/
static void
ExecutePlan(EState *estate,//執行狀態
PlanState *planstate,//計劃狀態
bool use_parallel_mode,//是否使用並行模式
CmdType operation,//操作型別
bool sendTuples,//是否需要傳輸元組
uint64 numberTuples,//元組數量
ScanDirection direction,//掃描方向
DestReceiver *dest,//接收的目標端
bool execute_once)//是否只執行一次
{
TupleTableSlot *slot;//元組表Slot
uint64 current_tuple_count;//當前的元組計數
/*
* initialize local variables
* 初始化本地變數
*/
current_tuple_count = 0;
/*
* Set the direction.
* 設定掃描方向
*/
estate->es_direction = direction;
/*
* If the plan might potentially be executed multiple times, we must force
* it to run without parallelism, because we might exit early.
* 如果計劃可能被多次執行,那麼必須強制它在非並行的情況下執行,因為可能會提前退出。
*/
if (!execute_once)
use_parallel_mode = false;//如需多次執行,則不允許並行執行
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
EnterParallelMode();//如並行,則進入並行模式
/*
* Loop until we've processed the proper number of tuples from the plan.
* 迴圈直至執行計劃已處理完成相應數量的元組
* 注意:每次迴圈只處理一個元組,每次都要重置元組Expr的上下文/過濾不需要的列/傳送元組
*/
for (;;)
{
/* Reset the per-output-tuple exprcontext */
//重置Expr上下文
ResetPerTupleExprContext(estate);
/*
* Execute the plan and obtain a tuple
* 執行計劃,獲取一個元組
*/
slot = ExecProcNode(planstate);
/*
* if the tuple is null, then we assume there is nothing more to
* process so we just end the loop...
* 如果返回的元組為空,那麼可以認為沒有什麼要處理的了,結束迴圈……
*/
if (TupIsNull(slot))
{
/*
* If we know we won't need to back up, we can release resources
* at this point.
* 如果已知不需要備份(回溯),那麼可以釋放資源了
*/
if (!(estate->es_top_eflags & EXEC_FLAG_BACKWARD))
(void) ExecShutdownNode(planstate);
break;
}
/*
* If we have a junk filter, then project a new tuple with the junk
* removed.
* 如有junk過濾器,使用junk執行投影操作,產生一個新的元組
*
* Store this new "clean" tuple in the junkfilter's resultSlot.
* (Formerly, we stored it back over the "dirty" tuple, which is WRONG
* because that tuple slot has the wrong descriptor.)
* 將這個新的“clean”元組儲存在junkfilter的resultSlot中。
* (以前,將其儲存在“dirty” tuple上,這是錯誤的,因為該tuple slot的描述符是錯誤的。)
*/
if (estate->es_junkFilter != NULL)
slot = ExecFilterJunk(estate->es_junkFilter, slot);
/*
* If we are supposed to send the tuple somewhere, do so. (In
* practice, this is probably always the case at this point.)
* 如果要將元組傳送到某個地方(接收器),那麼就這樣做。
* (實際上,在這一點上可能總是如此。)
*/
if (sendTuples)
{
/*
* If we are not able to send the tuple, we assume the destination
* has closed and no more tuples can be sent. If that's the case,
* end the loop.
* 如果不能傳送元組,有理由假設目的接收器已經關閉,不能傳送更多元組,結束迴圈。
*/
if (!dest->receiveSlot(slot, dest))
break;//跳出迴圈
}
/*
* Count tuples processed, if this is a SELECT. (For other operation
* types, the ModifyTable plan node must count the appropriate
* events.)
* 如果操作型別為CMD_SELECT,則計算已處理的元組。
* (對於其他操作型別,ModifyTable plan節點必須統計合適的事件。)
*/
if (operation == CMD_SELECT)
(estate->es_processed)++;
/*
* 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.
* 檢查處理的元組計數…
* 如果已完成處理,那麼退出,否則再次迴圈並處理更多元組。
* 注意:numberTuples=0表示沒有限制。
*/
current_tuple_count++;
if (numberTuples && numberTuples == current_tuple_count)
{
/*
* If we know we won't need to back up, we can release resources
* at this point.
* 不需要回溯,可以在此時釋放資源。
*/
if (!(estate->es_top_eflags & EXEC_FLAG_BACKWARD))
(void) ExecShutdownNode(planstate);
break;
}
}
if (use_parallel_mode)
ExitParallelMode();//退出並行模式
}
/* ----------------------------------------------------------------
* ExecProcNode
*
* Execute the given node to return a(nother) tuple.
* 呼叫node->ExecProcNode函式返回元組(one or another)
* ----------------------------------------------------------------
*/
#ifndef FRONTEND
static inline TupleTableSlot *
ExecProcNode(PlanState *node)
{
if (node->chgParam != NULL) /* 引數變化?something changed? */
ExecReScan(node); /* 呼叫ExecReScan函式;let ReScan handle this */
return node->ExecProcNode(node);//執行ExecProcNode
}
#endif
ExecSeqScan
ExecSeqScan函式順序掃描relation,返回下一個符合條件的元組。
/* ----------------------------------------------------------------
* ExecSeqScan(node)
*
* Scans the relation sequentially and returns the next qualifying
* tuple.
* We call the ExecScan() routine and pass it the appropriate
* access method functions.
* 順序掃描relation,返回下一個符合條件的元組。
* 呼叫ExecScan函式,傳入相應的訪問方法函式
* ----------------------------------------------------------------
*/
static TupleTableSlot *
ExecSeqScan(PlanState *pstate)
{
SeqScanState *node = castNode(SeqScanState, pstate);//獲取SeqScanState
return ExecScan(&node->ss,
(ExecScanAccessMtd) SeqNext,
(ExecScanRecheckMtd) SeqRecheck);//執行Scan
}
/* ----------------------------------------------------------------
* ExecScan
*
* Scans the relation using the 'access method' indicated and
* returns the next qualifying tuple in the direction specified
* in the global variable ExecDirection.
* The access method returns the next tuple and ExecScan() is
* responsible for checking the tuple returned against the qual-clause.
* 使用指定的“訪問方法”掃描關係,並按照全域性變數ExecDirection中指定的方向返回下一個符合條件的元組。
* 訪問方法返回下一個元組,ExecScan()負責根據qual-clause條件子句檢查返回的元組是否符合條件。
*
* A 'recheck method' must also be provided that can check an
* arbitrary tuple of the relation against any qual conditions
* that are implemented internal to the access method.
* 呼叫者還必須提供“recheck method”,根據訪問方法內部實現的條件檢查關係的所有元組。
*
* Conditions:
* -- the "cursor" maintained by the AMI is positioned at the tuple
* returned previously.
* 前提條件:
* 由AMI負責維護的遊標已由先前的處理過程定位.
*
* Initial States:
* -- the relation indicated is opened for scanning so that the
* "cursor" is positioned before the first qualifying tuple.
* 初始狀態:
* 在遊標可定位返回第一個符合條件的元組前,relation已開啟可進行掃描
* ----------------------------------------------------------------
*/
TupleTableSlot *
ExecScan(ScanState *node,
ExecScanAccessMtd accessMtd, /* 返回元組的訪問方法;function returning a tuple */
ExecScanRecheckMtd recheckMtd) //recheck方法
{
ExprContext *econtext;//表示式上下文
ExprState *qual;//表示式狀態
ProjectionInfo *projInfo;//投影資訊
/*
* Fetch data from node
* 從node中提取資料
*/
qual = node->ps.qual;
projInfo = node->ps.ps_ProjInfo;
econtext = node->ps.ps_ExprContext;
/* interrupt checks are in ExecScanFetch */
//在ExecScanFetch中有中斷檢查
/*
* If we have neither a qual to check nor a projection to do, just skip
* all the overhead and return the raw scan tuple.
* 如果既沒有要檢查的條件qual,也沒有要做的投影操作,那麼就跳過所有的操作並返回raw scan元組。
*/
if (!qual && !projInfo)
{
ResetExprContext(econtext);
return ExecScanFetch(node, accessMtd, recheckMtd);
}
/*
* Reset per-tuple memory context to free any expression evaluation
* storage allocated in the previous tuple cycle.
* 重置每個元組記憶體上下文,以釋放用於在前一個元組迴圈中分配的表示式求值記憶體空間。
*/
ResetExprContext(econtext);
/*
* get a tuple from the access method. Loop until we obtain a tuple that
* passes the qualification.
* 從訪問方法中獲取一個元組。迴圈,直到獲得透過限定條件的元組。
*/
for (;;)
{
TupleTableSlot *slot;//slot變數
slot = ExecScanFetch(node, accessMtd, recheckMtd);//獲取slot
/*
* if the slot returned by the accessMtd contains NULL, then it means
* there is nothing more to scan so we just return an empty slot,
* being careful to use the projection result slot so it has correct
* tupleDesc.
* 如果accessMtd方法返回的slot中包含NULL,那麼這意味著不再需要掃描了,
* 這時候只需要返回一個空slot,小心使用投影結果slot,這樣可以有正確的tupleDesc了。
*/
if (TupIsNull(slot))
{
if (projInfo)
return ExecClearTuple(projInfo->pi_state.resultslot);
else
return slot;
}
/*
* place the current tuple into the expr context
* 把當前tuple放入到expr上下文中
*/
econtext->ecxt_scantuple = slot;
/*
* check that the current tuple satisfies the qual-clause
* 檢查當前的tuple是否符合qual-clause條件
*
* check for non-null qual here to avoid a function call to ExecQual()
* when the qual is null ... saves only a few cycles, but they add up
* ...
* 在這裡檢查qual是否非空,以避免在qual為空時呼叫ExecQual()函式…
* 只節省了幾個呼叫週期,但它們加起來……的成本還是蠻可觀的
*/
if (qual == NULL || ExecQual(qual, econtext))
{
/*
* Found a satisfactory scan tuple.
* 發現一個滿足條件的元組
*/
if (projInfo)
{
/*
* Form a projection tuple, store it in the result tuple slot
* and return it.
* 構造一個投影元組,儲存在結果元組slot中並返回
*/
return ExecProject(projInfo);//執行投影操作並返回
}
else
{
/*
* Here, we aren't projecting, so just return scan tuple.
* 不需要執行投影操作,返回元組
*/
return slot;//直接返回
}
}
else
InstrCountFiltered1(node, 1);//instrument計數
/*
* Tuple fails qual, so free per-tuple memory and try again.
* 元組不滿足條件,釋放資源,重試
*/
ResetExprContext(econtext);
}
}
/*
* ExecScanFetch -- check interrupts & fetch next potential tuple
* ExecScanFetch -- 檢查中斷&提前下一個備選元組
*
* This routine is concerned with substituting a test tuple if we are
* inside an EvalPlanQual recheck. If we aren't, just execute
* the access method's next-tuple routine.
* 這個例程是處理測試元組的替換(如果在EvalPlanQual重新檢查中)。
* 如果不是在EvalPlanQual中,則執行access方法的next-tuple例程。
*/
static inline TupleTableSlot *
ExecScanFetch(ScanState *node,
ExecScanAccessMtd accessMtd,
ExecScanRecheckMtd recheckMtd)
{
EState *estate = node->ps.state;
CHECK_FOR_INTERRUPTS();//檢查中斷
if (estate->es_epqTuple != NULL)//如es_epqTuple不為NULL()
{
//es_epqTuple欄位用於在READ COMMITTED模式中替換更新後的元組後,重新評估是否滿足執行計劃的條件quals
/*
* We are inside an EvalPlanQual recheck. Return the test tuple if
* one is available, after rechecking any access-method-specific
* conditions.
* 我們正在EvalPlanQual複查。
* 如果test tuple可用,則在重新檢查所有特定於訪問方法的條件後返回該元組。
*/
Index scanrelid = ((Scan *) node->ps.plan)->scanrelid;//訪問的relid
if (scanrelid == 0)//relid==0
{
TupleTableSlot *slot = node->ss_ScanTupleSlot;
/*
* This is a ForeignScan or CustomScan which has pushed down a
* join to the remote side. The recheck method is responsible not
* only for rechecking the scan/join quals but also for storing
* the correct tuple in the slot.
* 這是一個ForeignScan或CustomScan,它將下推到遠端端。
* recheck方法不僅負責重新檢查掃描/連線quals,還負責在slot中儲存正確的元組。
*/
if (!(*recheckMtd) (node, slot))
ExecClearTuple(slot); /* 驗證不透過,釋放資源,不返回元組;would not be returned by scan */
return slot;
}
else if (estate->es_epqTupleSet[scanrelid - 1])//從estate->es_epqTupleSet陣列中獲取標誌
{
TupleTableSlot *slot = node->ss_ScanTupleSlot;//獲取slot
/* Return empty slot if we already returned a tuple */
//如已返回元組,則清空slot
if (estate->es_epqScanDone[scanrelid - 1])
return ExecClearTuple(slot);
/* Else mark to remember that we shouldn't return more */
//否則,標記沒有返回
estate->es_epqScanDone[scanrelid - 1] = true;
/* Return empty slot if we haven't got a test tuple */
//如test tuple為NULL,則清空slot
if (estate->es_epqTuple[scanrelid - 1] == NULL)
return ExecClearTuple(slot);
/* Store test tuple in the plan node's scan slot */
//在計劃節點的scan slot中儲存test tuple
ExecStoreHeapTuple(estate->es_epqTuple[scanrelid - 1],
slot, false);
/* Check if it meets the access-method conditions */
//檢查是否滿足訪問方法條件
if (!(*recheckMtd) (node, slot))
ExecClearTuple(slot); /* 不滿足,清空slot;would not be returned by scan */
return slot;
}
}
/*
* Run the node-type-specific access method function to get the next tuple
* 執行node-type-specific方法函式,獲取下一個tuple
*/
return (*accessMtd) (node);
}
/*
* ExecProject
*
* Projects a tuple based on projection info and stores it in the slot passed
* to ExecBuildProjectInfo().
* 根據投影資訊投影一個元組,並將其儲存在傳遞給ExecBuildProjectInfo()的slot中。
*
* Note: the result is always a virtual tuple; therefore it may reference
* the contents of the exprContext's scan tuples and/or temporary results
* constructed in the exprContext. If the caller wishes the result to be
* valid longer than that data will be valid, he must call ExecMaterializeSlot
* on the result slot.
* 注意:結果總是一個虛擬元組;
* 因此,它可以引用exprContext的掃描元組和/或exprContext中構造的臨時結果的內容。
* 如果呼叫者希望結果有效的時間長於資料有效的時間,必須在結果slot上呼叫ExecMaterializeSlot。
*/
#ifndef FRONTEND
static inline TupleTableSlot *
ExecProject(ProjectionInfo *projInfo)
{
ExprContext *econtext = projInfo->pi_exprContext;
ExprState *state = &projInfo->pi_state;
TupleTableSlot *slot = state->resultslot;
bool isnull;
/*
* Clear any former contents of the result slot. This makes it safe for
* us to use the slot's Datum/isnull arrays as workspace.
* 清除以前的結果slot內容。
* 這使得我們可以安全地使用slot的Datum/isnull陣列作為工作區。
*/
ExecClearTuple(slot);
/* Run the expression, discarding scalar result from the last column. */
//執行表示式,從最後一列丟棄scalar結果。
(void) ExecEvalExprSwitchContext(state, econtext, &isnull);
/*
* Successfully formed a result row. Mark the result slot as containing a
* valid virtual tuple (inlined version of ExecStoreVirtualTuple()).
* 成功形成了一個結果行。
* 將結果slot標記為包含一個有效的虛擬元組(ExecStoreVirtualTuple()的內聯版本)。
*/
slot->tts_flags &= ~TTS_FLAG_EMPTY;
slot->tts_nvalid = slot->tts_tupleDescriptor->natts;
return slot;
}
#endif
/*
* ExecQual - evaluate a qual prepared with ExecInitQual (possibly via
* ExecPrepareQual). Returns true if qual is satisfied, else false.
* 解析用ExecInitQual準備的條件qual(可能透過ExecPrepareQual)。
* 如果滿足條件qual,返回true,否則為false。
*
* Note: ExecQual used to have a third argument "resultForNull". The
* behavior of this function now corresponds to resultForNull == false.
* If you want the resultForNull == true behavior, see ExecCheck.
* 注意:ExecQual曾經有第三個引數“resultForNull”。
* 這個函式的行為現在對應於resultForNull == false。
* 如果希望resultForNull == true行為,請參閱ExecCheck。
*/
#ifndef FRONTEND
static inline bool
ExecQual(ExprState *state, ExprContext *econtext)
{
Datum ret;
bool isnull;
/* short-circuit (here and in ExecInitQual) for empty restriction list */
//如state為NULL,直接返回
if (state == NULL)
return true;
/* verify that expression was compiled using ExecInitQual */
//使用函式ExecInitQual驗證表示式是否可以編譯
Assert(state->flags & EEO_FLAG_IS_QUAL);
ret = ExecEvalExprSwitchContext(state, econtext, &isnull);
/* EEOP_QUAL不應返回NULL;EEOP_QUAL should never return NULL */
Assert(!isnull);
return DatumGetBool(ret);
}
#endif
/* --------------------------------
* ExecClearTuple
*
* This function is used to clear out a slot in the tuple table.
* 該函式清空tuple table中的slot
* NB: only the tuple is cleared, not the tuple descriptor (if any).
* 注意:只有tuple被清除,而不是tuple描述符
* --------------------------------
*/
TupleTableSlot * /* 返回驗證透過的slot;return: slot passed */
ExecClearTuple(TupleTableSlot *slot) /* 儲存tuple的slot;slot in which to store tuple */
{
/*
* sanity checks
* 安全檢查
*/
Assert(slot != NULL);
/*
* Free the old physical tuple if necessary.
* 如需要,釋放原有的物理元組
*/
if (TTS_SHOULDFREE(slot))
{
heap_freetuple(slot->tts_tuple);//釋放元組
slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
}
if (TTS_SHOULDFREEMIN(slot))
{
heap_free_minimal_tuple(slot->tts_mintuple);
slot->tts_flags &= ~TTS_FLAG_SHOULDFREEMIN;
}
slot->tts_tuple = NULL;//設定NULL值
slot->tts_mintuple = NULL;
/*
* Drop the pin on the referenced buffer, if there is one.
* 如果有的話,將pin放在已引用的緩衝區上。
*/
if (BufferIsValid(slot->tts_buffer))
ReleaseBuffer(slot->tts_buffer);//釋放緩衝區
slot->tts_buffer = InvalidBuffer;
/*
* Mark it empty.
* 標記為空
*/
slot->tts_flags |= TTS_FLAG_EMPTY;
slot->tts_nvalid = 0;
return slot;
}
三、跟蹤分析
測試指令碼如下
testdb=# explain select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je
testdb-# from t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je
testdb(# from t_grxx gr inner join t_jfxx jf
testdb(# on gr.dwbh = dw.dwbh
testdb(# and gr.grbh = jf.grbh) grjf
testdb-# order by dw.dwbh;
QUERY PLAN
------------------------------------------------------------------------------------------
Sort (cost=20070.93..20320.93 rows=100000 width=47)
Sort Key: dw.dwbh
-> Hash Join (cost=3754.00..8689.61 rows=100000 width=47)
Hash Cond: ((gr.dwbh)::text = (dw.dwbh)::text)
-> Hash Join (cost=3465.00..8138.00 rows=100000 width=31)
Hash Cond: ((jf.grbh)::text = (gr.grbh)::text)
-> Seq Scan on t_jfxx jf (cost=0.00..1637.00 rows=100000 width=20)
-> Hash (cost=1726.00..1726.00 rows=100000 width=16)
-> Seq Scan on t_grxx gr (cost=0.00..1726.00 rows=100000 width=16)
-> Hash (cost=164.00..164.00 rows=10000 width=20)
-> Seq Scan on t_dwxx dw (cost=0.00..164.00 rows=10000 width=20)
(11 rows)
啟動gdb,設定斷點,進入ExecutePlan
(gdb) b ExecutePlan
Breakpoint 1 at 0x6db79d: file execMain.c, line 1694.
(gdb) c
Continuing.
Breakpoint 1, ExecutePlan (estate=0x14daf48, planstate=0x14db160, use_parallel_mode=false, operation=CMD_SELECT,
sendTuples=true, numberTuples=0, direction=ForwardScanDirection, dest=0x14d9ed0, execute_once=true) at execMain.c:1694
warning: Source file is more recent than executable.
1694 current_tuple_count = 0;
檢視輸入引數
planstate->type:T_SortState->排序Plan
planstate->ExecProcNode:ExecProcNodeFirst,封裝器
planstate->ExecProcNodeReal:ExecSort,實際的函式
use_parallel_mode:false,非並行模式
operation:CMD_SELECT,查詢操作
sendTuples:T,需要傳送元組給客戶端
numberTuples:0,所有元組
direction:ForwardScanDirection
dest:printtup(console客戶端)
execute_once:T,只執行一次
(gdb) p *estate
$1 = {type = T_EState, es_direction = ForwardScanDirection, es_snapshot = 0x1493e10, es_crosscheck_snapshot = 0x0,
es_range_table = 0x14d7c00, es_plannedstmt = 0x14d9d58,
es_sourceText = 0x13eeeb8 "select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je \nfrom t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je \n", ' ' <repeats 24 times>, "from t_grxx gr inner join t_jfxx jf \n", ' ' <repeats 34 times>...,
es_junkFilter = 0x0, es_output_cid = 0, es_result_relations = 0x0, es_num_result_relations = 0,
es_result_relation_info = 0x0, es_root_result_relations = 0x0, es_num_root_result_relations = 0,
es_tuple_routing_result_relations = 0x0, es_trig_target_relations = 0x0, es_trig_tuple_slot = 0x0,
es_trig_oldtup_slot = 0x0, es_trig_newtup_slot = 0x0, es_param_list_info = 0x0, es_param_exec_vals = 0x0,
es_queryEnv = 0x0, es_query_cxt = 0x14dae30, es_tupleTable = 0x14dbaf8, es_rowMarks = 0x0, es_processed = 0,
es_lastoid = 0, es_top_eflags = 16, es_instrument = 0, es_finished = false, es_exprcontexts = 0x14db550,
es_subplanstates = 0x0, es_auxmodifytables = 0x0, es_per_tuple_exprcontext = 0x0, es_epqTuple = 0x0,
es_epqTupleSet = 0x0, es_epqScanDone = 0x0, es_use_parallel_mode = false, es_query_dsa = 0x0, es_jit_flags = 0,
es_jit = 0x0, es_jit_worker_instr = 0x0}
(gdb) p *planstate
$2 = {type = T_SortState, plan = 0x14d3f90, state = 0x14daf48, ExecProcNode = 0x6e41bb <ExecProcNodeFirst>,
ExecProcNodeReal = 0x716144 <ExecSort>, instrument = 0x0, worker_instrument = 0x0, worker_jit_instrument = 0x0,
qual = 0x0, lefttree = 0x14db278, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0,
ps_ResultTupleSlot = 0x14ec470, ps_ExprContext = 0x0, ps_ProjInfo = 0x0, scandesc = 0x14e9fd0}
(gdb) p *dest
$4 = {receiveSlot = 0x48cc00 <printtup>, rStartup = 0x48c5c1 <printtup_startup>, rShutdown = 0x48d02e <printtup_shutdown>,
rDestroy = 0x48d0a7 <printtup_destroy>, mydest = DestRemote}
賦值,準備執行ExecProcNode(ExecSort)
(gdb) n
1699 estate->es_direction = direction;
(gdb)
1705 if (!execute_once)
(gdb)
1708 estate->es_use_parallel_mode = use_parallel_mode;
(gdb)
1709 if (use_parallel_mode)
(gdb)
1718 ResetPerTupleExprContext(estate);
(gdb)
1723 slot = ExecProcNode(planstate);
(gdb)
執行ExecProcNode(ExecSort),返回slot
(gdb)
1729 if (TupIsNull(slot))
(gdb) p *slot
$5 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = false,
tts_tuple = 0x14ec4b0, tts_tupleDescriptor = 0x14ec058, tts_mcxt = 0x14dae30, tts_buffer = 0, tts_nvalid = 0,
tts_values = 0x14ec4d0, tts_isnull = 0x14ec508, tts_mintuple = 0x1a4b078, tts_minhdr = {t_len = 64, t_self = {ip_blkid = {
bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x1a4b070}, tts_off = 0,
tts_fixedTupleDescriptor = true}
檢視slot中的資料
注意:slot中的t_data不是實際的tuple data,而是緩衝區資訊,在返回時根據這些資訊從緩衝區獲取資料返回
(gdb) p *slot
$5 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = false,
tts_tuple = 0x14ec4b0, tts_tupleDescriptor = 0x14ec058, tts_mcxt = 0x14dae30, tts_buffer = 0, tts_nvalid = 0,
tts_values = 0x14ec4d0, tts_isnull = 0x14ec508, tts_mintuple = 0x1a4b078, tts_minhdr = {t_len = 64, t_self = {ip_blkid = {
bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x1a4b070}, tts_off = 0,
tts_fixedTupleDescriptor = true}
(gdb) p *slot->tts_tuple
$6 = {t_len = 64, t_self = {ip_blkid = {bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x1a4b070}
(gdb) p *slot->tts_tuple->t_data
$7 = {t_choice = {t_heap = {t_xmin = 21967600, t_xmax = 0, t_field3 = {t_cid = 56, t_xvac = 56}}, t_datum = {
datum_len_ = 21967600, datum_typmod = 0, datum_typeid = 56}}, t_ctid = {ip_blkid = {bi_hi = 0, bi_lo = 0},
ip_posid = 32639}, t_infomask2 = 7, t_infomask = 2, t_hoff = 24 '\030', t_bits = 0x1a4b087 ""}
判斷是否需要過濾屬性(不需要)
(gdb) n
1748 if (estate->es_junkFilter != NULL)
(gdb)
(gdb) p estate->es_junkFilter
$12 = (JunkFilter *) 0x0
修改計數器等資訊
(gdb)
1755 if (sendTuples)
(gdb)
1762 if (!dest->receiveSlot(slot, dest))
(gdb)
1771 if (operation == CMD_SELECT)
(gdb)
1772 (estate->es_processed)++;
(gdb) p estate->es_processed
$9 = 0
(gdb) n
1779 current_tuple_count++;
(gdb) p current_tuple_count
$10 = 0
(gdb) n
1780 if (numberTuples && numberTuples == current_tuple_count)
(gdb) p numberTuples
$11 = 0
(gdb) n
1790 }
繼續迴圈,直接滿足條件(全部掃描完畢)未知
(gdb) n
1718 ResetPerTupleExprContext(estate);
(gdb)
1723 slot = ExecProcNode(planstate);
(gdb)
1729 if (TupIsNull(slot))
...
ExecutePlan的主體邏輯已介紹完畢,下面簡單跟蹤分析ExecSeqScan函式
設定斷點,進入ExecSeqScan
(gdb) del 1
(gdb) c
Continuing.
Breakpoint 2, ExecSeqScan (pstate=0x14e99a0) at nodeSeqscan.c:127
warning: Source file is more recent than executable.
127 SeqScanState *node = castNode(SeqScanState, pstate);
檢視輸入引數
plan為SeqScan
ExecProcNode=ExecProcNodeReal,均為函式ExecSeqScan
targetlist為投影列資訊
(gdb) p *pstate
$13 = {type = T_SeqScanState, plan = 0x14d5570, state = 0x14daf48, ExecProcNode = 0x714d59 <ExecSeqScan>,
ExecProcNodeReal = 0x714d59 <ExecSeqScan>, instrument = 0x0, worker_instrument = 0x0, worker_jit_instrument = 0x0,
qual = 0x0, lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0,
ps_ResultTupleSlot = 0x14e9c38, ps_ExprContext = 0x14e9ab8, ps_ProjInfo = 0x0, scandesc = 0x7fa45b442ab8}
(gdb) p *pstate->plan
$14 = {type = T_SeqScan, startup_cost = 0, total_cost = 164, plan_rows = 10000, plan_width = 20, parallel_aware = false,
parallel_safe = true, plan_node_id = 7, targetlist = 0x14d5438, qual = 0x0, lefttree = 0x0, righttree = 0x0,
initPlan = 0x0, extParam = 0x0, allParam = 0x0}
進入ExecScan函式
accessMtd方法為SeqNext
recheckMtd方法為SeqRecheck
(gdb) n
129 return ExecScan(&node->ss,
(gdb) step
ExecScan (node=0x14e99a0, accessMtd=0x714c6d <SeqNext>, recheckMtd=0x714d3d <SeqRecheck>) at execScan.c:132
warning: Source file is more recent than executable.
132 qual = node->ps.qual;
ExecScan->投影資訊,為NULL
(gdb) p *projInfo
Cannot access memory at address 0x0
ExecScan->約束條件為NULL
(gdb) p *qual
Cannot access memory at address 0x0
ExecScan->如果既沒有要檢查的條件qual,也沒有要做的投影操作,那麼就跳過所有的操作並返回raw scan元組
(gdb) n
142 if (!qual && !projInfo)
(gdb)
144 ResetExprContext(econtext);
(gdb) n
145 return ExecScanFetch(node, accessMtd, recheckMtd);
ExecScan->進入ExecScanFetch
(gdb) step
ExecScanFetch (node=0x14e99a0, accessMtd=0x714c6d <SeqNext>, recheckMtd=0x714d3d <SeqRecheck>) at execScan.c:39
39 EState *estate = node->ps.state;
ExecScan->檢查中斷,判斷是否處於EvalPlanQual recheck狀態(為NULL,實際不是)
39 EState *estate = node->ps.state;
(gdb) n
41 CHECK_FOR_INTERRUPTS();
(gdb)
43 if (estate->es_epqTuple != NULL)
(gdb) p *estate->es_epqTuple
Cannot access memory at address 0x0
ExecScan->呼叫訪問方法SeqNext,返回slot
(gdb) n
95 return (*accessMtd) (node);
(gdb) n
96 }
ExecScan->回到ExecScan&ExecSeqScan,結束呼叫
(gdb) n
ExecScan (node=0x14e99a0, accessMtd=0x714c6d <SeqNext>, recheckMtd=0x714d3d <SeqRecheck>) at execScan.c:219
219 }
(gdb)
ExecSeqScan (pstate=0x14e99a0) at nodeSeqscan.c:132
132 }
(gdb)
DONE!
四、參考資料
PG Document:Query Planning
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/6906/viewspace-2374803/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- PostgreSQL 原始碼解讀(72)- 查詢語句#57(make_one_rel函式#22-...SQL原始碼函式
- PostgreSQL 原始碼解讀(24)- 查詢語句#9(查詢重寫)SQL原始碼
- PostgreSQL 原始碼解讀(20)- 查詢語句#5(查詢樹Query詳解)SQL原始碼
- PostgreSQL 原始碼解讀(18)- 查詢語句#3(SQL Parse)SQL原始碼
- PostgreSQL 原始碼解讀(19)- 查詢語句#4(ParseTree詳解)SQL原始碼
- PostgreSQL 原始碼解讀(25)- 查詢語句#10(查詢優化概覽)SQL原始碼優化
- PostgreSQL 原始碼解讀(17)- 查詢語句#2(查詢優化基礎)SQL原始碼優化
- PostgreSQL 原始碼解讀(83)- 查詢語句#68(PortalStart函式)SQL原始碼函式
- PostgreSQL 原始碼解讀(42)- 查詢語句#27(等價類)SQL原始碼
- PostgreSQL 原始碼解讀(74)- 查詢語句#59(Review - subquery_...SQL原始碼View
- PostgreSQL 原始碼解讀(75)- 查詢語句#60(Review - standard_...SQL原始碼View
- PostgreSQL 原始碼解讀(29)- 查詢語句#14(查詢優化-上拉子查詢)SQL原始碼優化
- PostgreSQL 原始碼解讀(90)- 查詢語句#75(ExecHashJoin函式#1)SQL原始碼函式
- PostgreSQL 原始碼解讀(91)- 查詢語句#76(ExecHashJoin函式#2)SQL原始碼函式
- PostgreSQL 原始碼解讀(93)- 查詢語句#77(ExecHashJoin函式#3)SQL原始碼函式
- PostgreSQL 原始碼解讀(81)- 查詢語句#66(Review - exec_simp...SQL原始碼View
- PostgreSQL 原始碼解讀(84)- 查詢語句#69(PortalStart->InitP...SQL原始碼
- PostgreSQL 原始碼解讀(85)- 查詢語句#70(PortalRun->InitPla...SQL原始碼
- PostgreSQL 原始碼解讀(86)- 查詢語句#71(PortalRun->PortalR...SQL原始碼
- PostgreSQL 原始碼解讀(50)- 查詢語句#35(Optimizer Review#1)SQL原始碼View
- PostgreSQL 原始碼解讀(51)- 查詢語句#36(Optimizer Review#2)SQL原始碼View
- PostgreSQL 原始碼解讀(36)- 查詢語句#21(查詢優化-消除外連線)SQL原始碼優化
- PostgreSQL 原始碼解讀(37)- 查詢語句#22(查詢優化-grouping_plan...SQL原始碼優化
- PostgreSQL 原始碼解讀(21)- 查詢語句#6(PlannedStmt詳解-跟蹤分析)SQL原始碼
- PostgreSQL 原始碼解讀(23)- 查詢語句#8(PlannedStmt與QUERY P...SQL原始碼
- PostgreSQL 原始碼解讀(167)- 查詢#87(基礎知識-語法分析器Bison)SQL原始碼語法分析
- PostgreSQL 原始碼解讀(16)- 查詢語句#1(基礎:關係代數)SQL原始碼
- PostgreSQL 原始碼解讀(95)- 查詢語句#78(ExecHashJoin函式#4-H...SQL原始碼函式
- PostgreSQL 原始碼解讀(97)- 查詢語句#79(ExecHashJoin函式#5-H...SQL原始碼函式
- PostgreSQL 原始碼解讀(88)- 查詢語句#73(SeqNext函式#1)SQL原始碼函式
- PostgreSQL 原始碼解讀(89)- 查詢語句#74(SeqNext函式#2)SQL原始碼函式
- PostgreSQL 原始碼解讀(46)- 查詢語句#31(query_planner函式#7)SQL原始碼函式
- PostgreSQL 原始碼解讀(47)- 查詢語句#32(query_planner函式#8)SQL原始碼函式
- PostgreSQL 原始碼解讀(48)- 查詢語句#33(query_planner函式#9)SQL原始碼函式
- PostgreSQL 原始碼解讀(41)- 查詢語句#26(query_planner函式#4)SQL原始碼函式
- PostgreSQL 原始碼解讀(40)- 查詢語句#25(query_planner函式#3)SQL原始碼函式
- PostgreSQL 原始碼解讀(43)- 查詢語句#28(query_planner函式#5)SQL原始碼函式
- PostgreSQL 原始碼解讀(45)- 查詢語句#30(query_planner函式#6)SQL原始碼函式