PostgreSQL 原始碼解讀(86)- 查詢語句#71(PortalRun->PortalR...
本節介紹了PortalRun->PortalRunSelect函式的實現邏輯,該函式執行以PORTAL_ONE_SELECT模式執行的SQL。
一、資料結構
Portal
對於Portals(客戶端請求),有幾種執行策略,具體取決於要執行什麼查詢。
(注意:無論什麼情況下,一個Portal只執行一個source-SQL查詢,因此從使用者的角度來看只產生一個結果。
/*
* We have several execution strategies for Portals, depending on what
* query or queries are to be executed. (Note: in all cases, a Portal
* executes just a single source-SQL query, and thus produces just a
* single result from the user's viewpoint. However, the rule rewriter
* may expand the single source query to zero or many actual queries.)
* 對於Portals(客戶端請求),有幾種執行策略,具體取決於要執行什麼查詢。
* (注意:無論什麼情況下,一個Portal只執行一個source-SQL查詢,因此從使用者的角度來看只產生一個結果。
* 但是,規則重寫器可以將單個源查詢擴充套件為零或多個實際查詢。
*
* PORTAL_ONE_SELECT: the portal contains one single SELECT query. We run
* the Executor incrementally as results are demanded. This strategy also
* supports holdable cursors (the Executor results can be dumped into a
* tuplestore for access after transaction completion).
* PORTAL_ONE_SELECT: 包含一個SELECT查詢。
* 按需要的結果重複(遞增)地執行執行器。
* 該策略還支援可持有遊標(執行器結果可以在事務完成後轉儲到tuplestore中進行訪問)。
*
* PORTAL_ONE_RETURNING: the portal contains a single INSERT/UPDATE/DELETE
* query with a RETURNING clause (plus possibly auxiliary queries added by
* rule rewriting). On first execution, we run the portal to completion
* and dump the primary query's results into the portal tuplestore; the
* results are then returned to the client as demanded. (We can't support
* suspension of the query partway through, because the AFTER TRIGGER code
* can't cope, and also because we don't want to risk failing to execute
* all the auxiliary queries.)
* PORTAL_ONE_RETURNING: 包含一個帶有RETURNING子句的INSERT/UPDATE/DELETE查詢
(可能還包括由規則重寫新增的輔助查詢)。
* 在第一次執行時,執行Portal來完成並將主查詢的結果轉儲到Portal的tuplestore中;
* 然後根據需要將結果返回給客戶端。
* (我們不能支援半途中斷的查詢,因為AFTER觸發器程式碼無法處理,
* 也因為不想冒執行所有輔助查詢失敗的風險)。
*
* PORTAL_ONE_MOD_WITH: the portal contains one single SELECT query, but
* it has data-modifying CTEs. This is currently treated the same as the
* PORTAL_ONE_RETURNING case because of the possibility of needing to fire
* triggers. It may act more like PORTAL_ONE_SELECT in future.
* PORTAL_ONE_MOD_WITH: 只包含一個SELECT查詢,但它具有資料修改的CTEs。
* 這與PORTAL_ONE_RETURNING的情況相同,因為可能需要觸發觸發器。將來它的行為可能更像PORTAL_ONE_SELECT。
*
* PORTAL_UTIL_SELECT: the portal contains a utility statement that returns
* a SELECT-like result (for example, EXPLAIN or SHOW). On first execution,
* we run the statement and dump its results into the portal tuplestore;
* the results are then returned to the client as demanded.
* PORTAL_UTIL_SELECT: 包含一個實用程式語句,該語句返回一個類似SELECT的結果(例如,EXPLAIN或SHOW)。
* 在第一次執行時,執行語句並將其結果轉儲到portal tuplestore;然後根據需要將結果返回給客戶端。
*
* PORTAL_MULTI_QUERY: all other cases. Here, we do not support partial
* execution: the portal's queries will be run to completion on first call.
* PORTAL_MULTI_QUERY: 除上述情況外的其他情況。
* 在這裡,不支援部分執行:Portal的查詢語句將在第一次呼叫時執行到完成。
*/
typedef enum PortalStrategy
{
PORTAL_ONE_SELECT,
PORTAL_ONE_RETURNING,
PORTAL_ONE_MOD_WITH,
PORTAL_UTIL_SELECT,
PORTAL_MULTI_QUERY
} PortalStrategy;
/*
* A portal is always in one of these states. It is possible to transit
* from ACTIVE back to READY if the query is not run to completion;
* otherwise we never back up in status.
* Portal總是處於這些狀態中的之一。
* 如果查詢沒有執行到完成,則可以從活動狀態轉回準備狀態;否則永遠不會後退。
*/
typedef enum PortalStatus
{
PORTAL_NEW, /* 剛建立;freshly created */
PORTAL_DEFINED, /* PortalDefineQuery完成;PortalDefineQuery done */
PORTAL_READY, /* PortalStart完成;PortalStart complete, can run it */
PORTAL_ACTIVE, /* Portal正在執行;portal is running (can't delete it) */
PORTAL_DONE, /* Portal已經完成;portal is finished (don't re-run it) */
PORTAL_FAILED /* Portal出現錯誤;portal got error (can't re-run it) */
} PortalStatus;
typedef struct PortalData *Portal;//結構體指標
typedef struct PortalData
{
/* Bookkeeping data */
const char *name; /* portal的名稱;portal's name */
const char *prepStmtName; /* 已完成準備的源語句;source prepared statement (NULL if none) */
MemoryContext portalContext; /* 記憶體上下文;subsidiary memory for portal */
ResourceOwner resowner; /* 資源的owner;resources owned by portal */
void (*cleanup) (Portal portal); /* cleanup鉤子函式;cleanup hook */
/*
* State data for remembering which subtransaction(s) the portal was
* created or used in. If the portal is held over from a previous
* transaction, both subxids are InvalidSubTransactionId. Otherwise,
* createSubid is the creating subxact and activeSubid is the last subxact
* in which we ran the portal.
* 狀態資料,用於記住在哪個子事務中建立或使用Portal。
* 如果Portal是從以前的事務中持有的,那麼兩個subxids都應該是InvalidSubTransactionId。
* 否則,createSubid是正在建立的subxact,而activeSubid是執行Portal的最後一個subxact。
*/
SubTransactionId createSubid; /* 正在建立的subxact;the creating subxact */
SubTransactionId activeSubid; /* 活動的最後一個subxact;the last subxact with activity */
/* The query or queries the portal will execute */
//portal將會執行的查詢
const char *sourceText; /* 查詢的源文字;text of query (as of 8.4, never NULL) */
const char *commandTag; /* 源查詢的命令tag;command tag for original query */
List *stmts; /* PlannedStmt連結串列;list of PlannedStmts */
CachedPlan *cplan; /* 快取的PlannedStmts;CachedPlan, if stmts are from one */
ParamListInfo portalParams; /* 傳遞給查詢的引數;params to pass to query */
QueryEnvironment *queryEnv; /* 查詢的執行環境;environment for query */
/* Features/options */
PortalStrategy strategy; /* 場景;see above */
int cursorOptions; /* DECLARE CURSOR選項位;DECLARE CURSOR option bits */
bool run_once; /* 是否只執行一次;portal will only be run once */
/* Status data */
PortalStatus status; /* Portal的狀態;see above */
bool portalPinned; /* 是否不能被清除;a pinned portal can't be dropped */
bool autoHeld; /* 是否自動從pinned到held;was automatically converted from pinned to
* held (see HoldPinnedPortals()) */
/* If not NULL, Executor is active; call ExecutorEnd eventually: */
//如不為NULL,執行器處於活動狀態
QueryDesc *queryDesc; /* 執行器需要使用的資訊;info needed for executor invocation */
/* If portal returns tuples, this is their tupdesc: */
//如Portal需要返回元組,這是元組的描述
TupleDesc tupDesc; /* 結果元組的描述;descriptor for result tuples */
/* and these are the format codes to use for the columns: */
//列資訊的格式碼
int16 *formats; /* 每一列的格式碼;a format code for each column */
/*
* Where we store tuples for a held cursor or a PORTAL_ONE_RETURNING or
* PORTAL_UTIL_SELECT query. (A cursor held past the end of its
* transaction no longer has any active executor state.)
* 在這裡,為持有的遊標或PORTAL_ONE_RETURNING或PORTAL_UTIL_SELECT儲存元組。
* (在事務結束後持有的遊標不再具有任何活動執行器狀態。)
*/
Tuplestorestate *holdStore; /* 儲存持有的遊標資訊;store for holdable cursors */
MemoryContext holdContext; /* 持有holdStore的記憶體上下文;memory containing holdStore */
/*
* Snapshot under which tuples in the holdStore were read. We must keep a
* reference to this snapshot if there is any possibility that the tuples
* contain TOAST references, because releasing the snapshot could allow
* recently-dead rows to be vacuumed away, along with any toast data
* belonging to them. In the case of a held cursor, we avoid needing to
* keep such a snapshot by forcibly detoasting the data.
* 讀取holdStore中元組的Snapshot。
* 如果元組包含TOAST引用的可能性存在,那麼必須保持對該快照的引用,
* 因為釋放快照可能會使最近廢棄的行與屬於它們的TOAST資料一起被清除。
* 對於持有的遊標,透過強制解壓資料來避免需要保留這樣的快照。
*/
Snapshot holdSnapshot; /* 已註冊的快照資訊,如無則為NULL;registered snapshot, or NULL if none */
/*
* atStart, atEnd and portalPos indicate the current cursor position.
* portalPos is zero before the first row, N after fetching N'th row of
* query. After we run off the end, portalPos = # of rows in query, and
* atEnd is true. Note that atStart implies portalPos == 0, but not the
* reverse: we might have backed up only as far as the first row, not to
* the start. Also note that various code inspects atStart and atEnd, but
* only the portal movement routines should touch portalPos.
* atStart、atEnd和portalPos表示當前游標的位置。
* portalPos在第一行之前為0,在獲取第N行查詢後為N。
* 在執行結束後,portalPos = #查詢中的行號,atEnd為T。
* 注意,atStart表示portalPos == 0,但不是相反:我們可能只回到到第一行,而不是開始。
* 還要注意,各種程式碼在開始和結束時都要檢查,但是隻有Portal移動例程應該訪問portalPos。
*/
bool atStart;//處於開始位置?
bool atEnd;//處於結束位置?
uint64 portalPos;//實際行號
/* Presentation data, primarily used by the pg_cursors system view */
//用於表示的資料,主要由pg_cursors系統檢視使用
TimestampTz creation_time; /* portal定義的時間;time at which this portal was defined */
bool visible; /* 是否在pg_cursors中可見? include this portal in pg_cursors? */
} PortalData;
/*
* PortalIsValid
* True iff portal is valid.
* 判斷Portal是否有效
*/
#define PortalIsValid(p) PointerIsValid(p)
QueryDesc
QueryDesc封裝了執行器執行查詢所需的所有內容。
/* ----------------
* query descriptor:
*
* a QueryDesc encapsulates everything that the executor
* needs to execute the query.
* QueryDesc封裝了執行器執行查詢所需的所有內容。
*
* For the convenience of SQL-language functions, we also support QueryDescs
* containing utility statements; these must not be passed to the executor
* however.
* 為了使用SQL函式,還需要支援包含實用語句的QueryDescs;
* 但是,這些內容不能傳遞給執行程式。
* ---------------------
*/
typedef struct QueryDesc
{
/* These fields are provided by CreateQueryDesc */
//以下變數由CreateQueryDesc函式設定
CmdType operation; /* 操作型別,如CMD_SELECT等;CMD_SELECT, CMD_UPDATE, etc. */
PlannedStmt *plannedstmt; /* 已規劃的語句,規劃器的輸出;planner's output (could be utility, too) */
const char *sourceText; /* 源SQL文字;source text of the query */
Snapshot snapshot; /* 查詢使用的快照;snapshot to use for query */
Snapshot crosscheck_snapshot; /* RI 更新/刪除交叉檢查快照;crosscheck for RI update/delete */
DestReceiver *dest; /* 元組輸出的接收器;the destination for tuple output */
ParamListInfo params; /* 需傳入的引數值;param values being passed in */
QueryEnvironment *queryEnv; /* 查詢環境變數;query environment passed in */
int instrument_options; /* InstrumentOption選項;OR of InstrumentOption flags */
/* These fields are set by ExecutorStart */
//以下變數由ExecutorStart函式設定
TupleDesc tupDesc; /* 結果元組tuples描述;descriptor for result tuples */
EState *estate; /* 執行器狀態;executor's query-wide state */
PlanState *planstate; /* per-plan-node狀態樹;tree of per-plan-node state */
/* This field is set by ExecutorRun */
//以下變數由ExecutorRun設定
bool already_executed; /* 先前已執行,則為T;true if previously executed */
/* This is always set NULL by the core system, but plugins can change it */
//核心設定為NULL,可由外掛修改
struct Instrumentation *totaltime; /* ExecutorRun函式所花費的時間;total time spent in ExecutorRun */
} QueryDesc;
二、原始碼解讀
PortalRun->PortalRunSelect函式執行以PORTAL_ONE_SELECT模式執行的SQL.
/*
* PortalRunSelect
* Execute a portal's query in PORTAL_ONE_SELECT mode, and also
* when fetching from a completed holdStore in PORTAL_ONE_RETURNING,
* PORTAL_ONE_MOD_WITH, and PORTAL_UTIL_SELECT cases.
* 執行以PORTAL_ONE_SELECT模式執行的SQL,同時處理PORTAL_ONE_RETURNING/
* PORTAL_ONE_MOD_WITH/PORTAL_UTIL_SELECT這幾種模式下完成holdStore後的資料提取
*
* This handles simple N-rows-forward-or-backward cases. For more complex
* nonsequential access to a portal, see PortalRunFetch.
* 這將處理簡單的n行前向或後向情況。
* 有關對門戶的更復雜的非順序訪問,請參閱PortalRunFetch。
*
* count <= 0 is interpreted as a no-op: the destination gets started up
* and shut down, but nothing else happens. Also, count == FETCH_ALL is
* interpreted as "all rows". (cf FetchStmt.howMany)
* count <= 0被解釋為一個no-op:目標啟動並關閉,但是沒有發生其他事情。
* 另外,count == FETCH_ALL被解釋為“所有行”。(cf FetchStmt.howMany)
*
* Caller must already have validated the Portal and done appropriate
* setup (cf. PortalRun).
* 呼叫者必須完成Portal的校驗以及相關的配置.
*
* Returns number of rows processed (suitable for use in result tag)
* 返回已處理的行數.
*/
static uint64
PortalRunSelect(Portal portal,
bool forward,
long count,
DestReceiver *dest)
{
QueryDesc *queryDesc;
ScanDirection direction;
uint64 nprocessed;
/*
* NB: queryDesc will be NULL if we are fetching from a held cursor or a
* completed utility query; can't use it in that path.
* 注意:從已持有的遊標或者已完成的工具類查詢中返回時,queryDesc有可能是NULL.
*/
queryDesc = portal->queryDesc;
/* Caller messed up if we have neither a ready query nor held data. */
//確保queryDescbuweiNULL或者持有提取的資料
Assert(queryDesc || portal->holdStore);
/*
* Force the queryDesc destination to the right thing. This supports
* MOVE, for example, which will pass in dest = DestNone. This is okay to
* change as long as we do it on every fetch. (The must not
* assume that dest never changes.)
* 確保queryDesc目的地是正確的地方。
* 例如,它支援MOVE,它將傳入dest = DestNone。
* 只要在每次取回時都這樣做,這是可以改變的。(Executor不能假定dest永不改變。)
*/
if (queryDesc)
queryDesc->dest = dest;//設定dest
/*
* Determine which direction to go in, and check to see if we're already
* at the end of the available tuples in that direction. If so, set the
* direction to NoMovement to avoid trying to fetch any tuples. (This
* check exists because not all plan node types are robust about being
* called again if they've already returned NULL once.) Then call the
* executor (we must not skip this, because the destination needs to see a
* setup and shutdown even if no tuples are available). Finally, update
* the portal position state depending on the number of tuples that were
* retrieved.
* 確定要進入的方向,並檢查是否已經在該方向的可用元組的末尾。
* 如果是這樣,則將方向設定為NoMovement,以避免試圖再次獲取任何元組。
* (之所以存在這種檢查,是因為不是所有的計劃節點型別都能夠在已經返回NULL時再次呼叫。)
* 然後呼叫executor(我們不能跳過這一步,因為目標需要看到設定和關閉,即使沒有元組可用)。
* 最後,根據檢索到的元組數量更新Portal的資料位置狀態。
*/
if (forward)//前向
{
if (portal->atEnd || count <= 0)
{
//已到末尾或者行計數小於等於0
direction = NoMovementScanDirection;
count = 0; /* don't pass negative count to executor */
}
else
direction = ForwardScanDirection;//前向掃描
/* In the executor, zero count processes all rows */
//在executor中,count=0意味著提取所有行
if (count == FETCH_ALL)
count = 0;
if (portal->holdStore)
//持有提取後的資料遊標
nprocessed = RunFromStore(portal, direction, (uint64) count, dest);
else
{
//沒有持有遊標(資料)
PushActiveSnapshot(queryDesc->snapshot);//快照入棧
ExecutorRun(queryDesc, direction, (uint64) count,
portal->run_once);//開始執行
nprocessed = queryDesc->estate->es_processed;//結果行數
PopActiveSnapshot();//快照出棧
}
if (!ScanDirectionIsNoMovement(direction))//掃描方向可移動
{
if (nprocessed > 0)//掃描行數>0
portal->atStart = false; /* 可以向前移動了;OK to go backward now */
if (count == 0 || nprocessed < (uint64) count)
//count為0或者行數小於傳入的計數器
portal->atEnd = true; /* 已完成掃描;we retrieved 'em all */
portal->portalPos += nprocessed;//位置移動(+處理行數)
}
}
else//非前向(後向)
{
if (portal->cursorOptions & CURSOR_OPT_NO_SCROLL)//如遊標不可移動,報錯
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cursor can only scan forward"),
errhint("Declare it with SCROLL option to enable backward scan.")));
if (portal->atStart || count <= 0)
{
//處於開始或者count小於等於0
direction = NoMovementScanDirection;
count = 0; /* don't pass negative count to executor */
}
else
//往後掃描
direction = BackwardScanDirection;
/* In the executor, zero count processes all rows */
//參見forward=T的註釋
if (count == FETCH_ALL)
count = 0;
if (portal->holdStore)
nprocessed = RunFromStore(portal, direction, (uint64) count, dest);
else
{
PushActiveSnapshot(queryDesc->snapshot);
ExecutorRun(queryDesc, direction, (uint64) count,
portal->run_once);
nprocessed = queryDesc->estate->es_processed;
PopActiveSnapshot();
}
if (!ScanDirectionIsNoMovement(direction))
{
if (nprocessed > 0 && portal->atEnd)
{
portal->atEnd = false; /* OK to go forward now */
portal->portalPos++; /* adjust for endpoint case */
}
if (count == 0 || nprocessed < (uint64) count)
{
portal->atStart = true; /* we retrieved 'em all */
portal->portalPos = 0;
}
else
{
portal->portalPos -= nprocessed;
}
}
}
return nprocessed;
}
/*
* RunFromStore
* Fetch tuples from the portal's tuple store.
* 從Portal的tuple store中提取元組.
*
* Calling conventions are similar to ExecutorRun, except that we
* do not depend on having a queryDesc or estate. Therefore we return the
* number of tuples processed as the result, not in estate->es_processed.
* 該函式的呼叫約定類似於ExecutorRun,只是不依賴於是否擁有queryDesc或estate。
* 因此,返回處理的元組的數量作為結果,而不是在estate->es_processed中返回。
*
* One difference from ExecutorRun is that the destination receiver functions
* are run in the caller's memory context (since we have no estate). Watch
* out for memory leaks.
* 與ExecutorRun不同的是,目標接收器函式在呼叫者的記憶體上下文中執行(因為沒有estate)。
* 需注意記憶體洩漏!!!
*/
static uint64
RunFromStore(Portal portal, ScanDirection direction, uint64 count,
DestReceiver *dest)
{
uint64 current_tuple_count = 0;
TupleTableSlot *slot;//元組表slot
slot = MakeSingleTupleTableSlot(portal->tupDesc);
dest->rStartup(dest, CMD_SELECT, portal->tupDesc);//目標啟動
if (ScanDirectionIsNoMovement(direction))//無法移動
{
/* do nothing except start/stop the destination */
//不需要做任何事情
}
else
{
bool forward = ScanDirectionIsForward(direction);//是否前向掃描
for (;;)//迴圈
{
MemoryContext oldcontext;//記憶體上下文
bool ok;
oldcontext = MemoryContextSwitchTo(portal->holdContext);//切換至相應的記憶體上下文
ok = tuplestore_gettupleslot(portal->holdStore, forward, false,
slot);//獲取元組
MemoryContextSwitchTo(oldcontext);//切換回原上下文
if (!ok)
break;//如出錯,則跳出迴圈
/*
* 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;
ExecClearTuple(slot);//執行清理
/*
* check our tuple count.. if we've processed the proper number
* then quit, else loop again and process more tuples. Zero count
* means no limit.
* 檢查元組計數…如果處理了正確的計數,那麼退出,
* 否則再次迴圈並處理更多元組。零計數意味著沒有限制。
*/
current_tuple_count++;
if (count && count == current_tuple_count)
break;
}
}
dest->rShutdown(dest);//關閉目標端
ExecDropSingleTupleTableSlot(slot);//清除slot
return current_tuple_count;//返回行數
}
/* ----------------------------------------------------------------
* ExecutorRun
* ExecutorRun函式
*
* This is the main routine of the executor module. It accepts
* the query descriptor from the traffic cop and executes the
* query plan.
* 這是executor模組的主要實現例程。它接受traffic cop的查詢描述符並執行查詢計劃。
*
* ExecutorStart must have been called already.
* 在此之前,已呼叫ExecutorStart函式.
*
* If direction is NoMovementScanDirection then nothing is done
* except to start up/shut down the destination. Otherwise,
* we retrieve up to 'count' tuples in the specified direction.
* 如果方向是NoMovementScanDirection,那麼除了啟動/關閉目標之外什麼也不做。
* 否則,在指定的方向上檢索指定數量“count”的元組。
*
* Note: count = 0 is interpreted as no portal limit, i.e., run to
* completion. Also note that the count limit is only applied to
* retrieved tuples, not for instance to those inserted/updated/deleted
* by a ModifyTable plan node.
* 注意:count = 0被解釋為沒有限制,即,執行到完成。
* 還要注意,計數限制只適用於檢索到的元組,而不適用於由ModifyTable計劃節點插入/更新/刪除的元組。
*
* There is no return value, but output tuples (if any) are sent to
* the destination receiver specified in the QueryDesc; and the number
* of tuples processed at the top level can be found in
* estate->es_processed.
* 沒有返回值,但是輸出元組(如果有的話)被髮送到QueryDesc中指定的目標接收器;
* 在頂層處理的元組數量可以在estate-> es_processing中找到。
*
* We provide a function hook variable that lets loadable plugins
* get control when ExecutorRun is called. Such a plugin would
* normally call standard_ExecutorRun().
* 我們提供了一個鉤子函式變數,可以讓外掛在呼叫ExecutorRun時獲得控制權。
* 這樣的外掛通常會呼叫standard_ExecutorRun()函式。
*
* ----------------------------------------------------------------
*/
void
ExecutorRun(QueryDesc *queryDesc,
ScanDirection direction, uint64 count,
bool execute_once)
{
if (ExecutorRun_hook)
(*ExecutorRun_hook) (queryDesc, direction, count, execute_once);//鉤子函式
else
standard_ExecutorRun(queryDesc, direction, count, execute_once);//標準函式
}
void
standard_ExecutorRun(QueryDesc *queryDesc,
ScanDirection direction, uint64 count, bool execute_once)
{
EState *estate;//全域性執行狀態
CmdType operation;//命令型別
DestReceiver *dest;//接收器
bool sendTuples;//是否需要傳輸元組
MemoryContext oldcontext;//記憶體上下文
/* sanity checks */
Assert(queryDesc != NULL);//校驗queryDesc不能為NULL
estate = queryDesc->estate;//獲取執行狀態
Assert(estate != NULL);//執行狀態不能為NULL
Assert(!(estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY));//eflags標記不能為EXEC_FLAG_EXPLAIN_ONLY
/*
* Switch into per-query memory context
* 切換記憶體上下文
*/
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
/* Allow instrumentation of Executor overall runtime */
//允許全程instrumentation
if (queryDesc->totaltime)
InstrStartNode(queryDesc->totaltime);
/*
* extract information from the query descriptor and the query feature.
* 從查詢描述符和查詢特性中提取資訊。
*/
operation = queryDesc->operation;
dest = queryDesc->dest;
/*
* startup tuple receiver, if we will be emitting tuples
* 如需傳送元組,則啟動元組接收器
*/
estate->es_processed = 0;
estate->es_lastoid = InvalidOid;
sendTuples = (operation == CMD_SELECT ||
queryDesc->plannedstmt->hasReturning);
if (sendTuples)//如需傳送元組
dest->rStartup(dest, operation, queryDesc->tupDesc);
/*
* run plan
* 執行Plan
*/
if (!ScanDirectionIsNoMovement(direction))//如非ScanDirectionIsNoMovement
{
if (execute_once && queryDesc->already_executed)//校驗
elog(ERROR, "can't re-execute query flagged for single execution");
queryDesc->already_executed = true;//修改標記
ExecutePlan(estate,
queryDesc->planstate,
queryDesc->plannedstmt->parallelModeNeeded,
operation,
sendTuples,
count,
direction,
dest,
execute_once);//執行Plan
}
/*
* shutdown tuple receiver, if we started it
* 如啟動了元組接收器,則關閉它
*/
if (sendTuples)
dest->rShutdown(dest);
if (queryDesc->totaltime)//收集時間
InstrStopNode(queryDesc->totaltime, estate->es_processed);
MemoryContextSwitchTo(oldcontext);//切換記憶體上下文
}
三、跟蹤分析
測試指令碼如下
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,設定斷點,進入PortalRunSelect
(gdb) b PortalRunSelect
Breakpoint 1 at 0x8cc0e8: file pquery.c, line 888.
(gdb) c
Continuing.
Breakpoint 1, PortalRunSelect (portal=0x1af2468, forward=true, count=9223372036854775807, dest=0x1b74668) at pquery.c:888
warning: Source file is more recent than executable.
888 queryDesc = portal->queryDesc;
(gdb)
檢視輸入引數portal&dest,forward為T表示前向掃描
portal:未命名的Portal,holdStore為NULL,atStart = true, atEnd = false, portalPos = 0
dest:接收器slot為printtup
(gdb) p *portal
$1 = {name = 0x1af5e90 "", prepStmtName = 0x0, portalContext = 0x1b795d0, resowner = 0x1abde80,
cleanup = 0x6711b6 <PortalCleanup>, createSubid = 1, activeSubid = 1,
sourceText = 0x1a8ceb8 "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>...,
commandTag = 0xc5eed5 "SELECT", stmts = 0x1b74630, cplan = 0x0, portalParams = 0x0, queryEnv = 0x0,
strategy = PORTAL_ONE_SELECT, cursorOptions = 4, run_once = true, status = PORTAL_ACTIVE, portalPinned = false,
autoHeld = false, queryDesc = 0x1b796e8, tupDesc = 0x1b867d8, formats = 0x1b79780, holdStore = 0x0, holdContext = 0x0,
holdSnapshot = 0x0, atStart = true, atEnd = false, portalPos = 0, creation_time = 595566906253867, visible = false}
(gdb) p *dest
$2 = {receiveSlot = 0x48cc00 <printtup>, rStartup = 0x48c5c1 <printtup_startup>, rShutdown = 0x48d02e <printtup_shutdown>,
rDestroy = 0x48d0a7 <printtup_destroy>, mydest = DestRemote}
校驗並設定dest
(gdb) n
891 Assert(queryDesc || portal->holdStore);
(gdb)
899 if (queryDesc)
(gdb)
900 queryDesc->dest = dest;
前向掃描
(gdb) n
913 if (forward)
(gdb)
915 if (portal->atEnd || count <= 0)
進入ExecutorRun
...
(gdb)
932 ExecutorRun(queryDesc, direction, (uint64) count,
(gdb) step
ExecutorRun (queryDesc=0x1b796e8, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:304
warning: Source file is more recent than executable.
304 if (ExecutorRun_hook)
進入standard_ExecutorRun
(gdb) n
307 standard_ExecutorRun(queryDesc, direction, count, execute_once);
(gdb) step
standard_ExecutorRun (queryDesc=0x1b796e8, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:321
321 Assert(queryDesc != NULL);
standard_ExecutorRun->校驗並切換上下文
321 Assert(queryDesc != NULL);
(gdb) n
323 estate = queryDesc->estate;
(gdb)
325 Assert(estate != NULL);
(gdb)
326 Assert(!(estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY));
(gdb)
331 oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
(gdb)
standard_ExecutorRun->變數賦值,判斷是否需要傳輸元組
(gdb)
334 if (queryDesc->totaltime)
(gdb) n
340 operation = queryDesc->operation;
(gdb)
341 dest = queryDesc->dest;
(gdb) p operation
$3 = CMD_SELECT
(gdb) n
346 estate->es_processed = 0;
(gdb)
347 estate->es_lastoid = InvalidOid;
(gdb)
349 sendTuples = (operation == CMD_SELECT ||
(gdb)
352 if (sendTuples)
(gdb)
353 dest->rStartup(dest, operation, queryDesc->tupDesc);
(gdb) p sendTuples
$4 = true
(gdb)
standard_ExecutorRun->執行計劃(ExecutePlan函式下節介紹)
(gdb) n
358 if (!ScanDirectionIsNoMovement(direction))
(gdb)
360 if (execute_once && queryDesc->already_executed)
(gdb)
362 queryDesc->already_executed = true;
(gdb)
364 ExecutePlan(estate,
(gdb)
standard_ExecutorRun->關閉資源並切換上下文
(gdb)
378 if (sendTuples)
(gdb) n
379 dest->rShutdown(dest);
(gdb)
381 if (queryDesc->totaltime)
(gdb)
384 MemoryContextSwitchTo(oldcontext);
(gdb)
385 }
(gdb)
standard_ExecutorRun->回到PortalRunSelect
(gdb) n
ExecutorRun (queryDesc=0x1b796e8, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:308
308 }
(gdb)
PortalRunSelect (portal=0x1af2468, forward=true, count=0, dest=0x1b74668) at pquery.c:934
934 nprocessed = queryDesc->estate->es_processed;
快照出棧,修改狀態atStart/atEnd等
(gdb) n
935 PopActiveSnapshot();
(gdb)
938 if (!ScanDirectionIsNoMovement(direction))
(gdb)
940 if (nprocessed > 0)
(gdb) p nprocessed
$6 = 99991
(gdb) n
941 portal->atStart = false; /* OK to go backward now */
(gdb)
942 if (count == 0 || nprocessed < (uint64) count)
(gdb)
完成呼叫
(gdb) n
943 portal->atEnd = true; /* we retrieved 'em all */
(gdb) p count
$7 = 0
(gdb) n
944 portal->portalPos += nprocessed;
(gdb)
997 return nprocessed;
(gdb)
998 }
(gdb) n
PortalRun (portal=0x1af2468, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x1b74668, altdest=0x1b74668,
completionTag=0x7ffc5ff58740 "") at pquery.c:780
780 if (completionTag && portal->commandTag)
(gdb) p nprocessed
$8 = 99991
DONE!
四、參考資料
PG Document:Query Planning
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/6906/viewspace-2374804/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- PostgreSQL 原始碼解讀(85)- 查詢語句#70(PortalRun->InitPla...SQL原始碼
- PostgreSQL 原始碼解讀(71)- 查詢語句#56(make_one_rel函式#21-...SQL原始碼函式
- PostgreSQL 原始碼解讀(24)- 查詢語句#9(查詢重寫)SQL原始碼
- PostgreSQL 原始碼解讀(20)- 查詢語句#5(查詢樹Query詳解)SQL原始碼
- PostgreSQL 原始碼解讀(18)- 查詢語句#3(SQL Parse)SQL原始碼
- PostgreSQL 原始碼解讀(19)- 查詢語句#4(ParseTree詳解)SQL原始碼
- PostgreSQL 原始碼解讀(17)- 查詢語句#2(查詢優化基礎)SQL原始碼優化
- PostgreSQL 原始碼解讀(25)- 查詢語句#10(查詢優化概覽)SQL原始碼優化
- PostgreSQL 原始碼解讀(83)- 查詢語句#68(PortalStart函式)SQL原始碼函式
- PostgreSQL 原始碼解讀(75)- 查詢語句#60(Review - standard_...SQL原始碼View
- PostgreSQL 原始碼解讀(74)- 查詢語句#59(Review - subquery_...SQL原始碼View
- PostgreSQL 原始碼解讀(42)- 查詢語句#27(等價類)SQL原始碼
- PostgreSQL 原始碼解讀(29)- 查詢語句#14(查詢優化-上拉子查詢)SQL原始碼優化
- PostgreSQL 原始碼解讀(37)- 查詢語句#22(查詢優化-grouping_plan...SQL原始碼優化
- PostgreSQL 原始碼解讀(82)- 查詢語句#67(PortalXXX系列函式)SQL原始碼函式
- PostgreSQL 原始碼解讀(81)- 查詢語句#66(Review - exec_simp...SQL原始碼View
- PostgreSQL 原始碼解讀(89)- 查詢語句#74(SeqNext函式#2)SQL原始碼函式
- PostgreSQL 原始碼解讀(90)- 查詢語句#75(ExecHashJoin函式#1)SQL原始碼函式
- PostgreSQL 原始碼解讀(91)- 查詢語句#76(ExecHashJoin函式#2)SQL原始碼函式
- PostgreSQL 原始碼解讀(88)- 查詢語句#73(SeqNext函式#1)SQL原始碼函式
- PostgreSQL 原始碼解讀(87)- 查詢語句#72(PortalRunSelect->E...SQL原始碼
- PostgreSQL 原始碼解讀(84)- 查詢語句#69(PortalStart->InitP...SQL原始碼
- PostgreSQL 原始碼解讀(93)- 查詢語句#77(ExecHashJoin函式#3)SQL原始碼函式
- PostgreSQL 原始碼解讀(50)- 查詢語句#35(Optimizer Review#1)SQL原始碼View
- PostgreSQL 原始碼解讀(51)- 查詢語句#36(Optimizer Review#2)SQL原始碼View
- PostgreSQL 原始碼解讀(36)- 查詢語句#21(查詢優化-消除外連線)SQL原始碼優化
- PostgreSQL 原始碼解讀(21)- 查詢語句#6(PlannedStmt詳解-跟蹤分析)SQL原始碼
- PostgreSQL 原始碼解讀(73)- 查詢語句#58(grouping_planner函式...SQL原始碼函式
- PostgreSQL 原始碼解讀(23)- 查詢語句#8(PlannedStmt與QUERY P...SQL原始碼
- PostgreSQL 原始碼解讀(95)- 查詢語句#78(ExecHashJoin函式#4-H...SQL原始碼函式
- PostgreSQL 原始碼解讀(97)- 查詢語句#79(ExecHashJoin函式#5-H...SQL原始碼函式
- PostgreSQL 原始碼解讀(16)- 查詢語句#1(基礎:關係代數)SQL原始碼
- PostgreSQL 原始碼解讀(43)- 查詢語句#28(query_planner函式#5)SQL原始碼函式
- PostgreSQL 原始碼解讀(45)- 查詢語句#30(query_planner函式#6)SQL原始碼函式
- PostgreSQL 原始碼解讀(46)- 查詢語句#31(query_planner函式#7)SQL原始碼函式
- PostgreSQL 原始碼解讀(47)- 查詢語句#32(query_planner函式#8)SQL原始碼函式
- PostgreSQL 原始碼解讀(48)- 查詢語句#33(query_planner函式#9)SQL原始碼函式
- PostgreSQL 原始碼解讀(38)- 查詢語句#23(query_planner函式#1)SQL原始碼函式