您好,登錄后才能下訂單哦!
本篇內容介紹了“PostgreSQL中PortalRun->PortalRunSelect函數的實現邏輯是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
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
“PostgreSQL中PortalRun->PortalRunSelect函數的實現邏輯是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。