PostgreSQL 原始碼解讀(82)- 查詢語句#67(PortalXXX系列函式)
本節介紹了PortalXXX函式,這些函式在create_simple_query中被呼叫,包括CreatePortal、PortalDefineQuery、PortalSetResultFormat、PortalRun和PortalDrop函式。
一、資料結構
Portal
包括場景PortalStrategy列舉定義/PortalStatus狀態定義/PortalData結構體.Portal是PortalData結構體指標,詳見程式碼註釋.
/*
* 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)
二、原始碼解讀
CreatePortal
CreatePortal函式建立給定名稱的Portal結構.
//------------------------------------------------------ CreatePortal
/*
* CreatePortal
* Returns a new portal given a name.
* 建立給定名稱的Portal結構
*
* allowDup: if true, automatically drop any pre-existing portal of the
* same name (if false, an error is raised).
* allowDup:如為true,則自動清除已存在的同名portal,如為F,則報錯
*
* dupSilent: if true, don't even emit a WARNING.
* dupSilent:如為T,不提示警告
*/
Portal
CreatePortal(const char *name, bool allowDup, bool dupSilent)
{
Portal portal;
AssertArg(PointerIsValid(name));
//根據給定的名稱獲取portal
portal = GetPortalByName(name);
if (PortalIsValid(portal))
{
//如portal有效
if (!allowDup)//不允許同名
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_CURSOR),
errmsg("cursor \"%s\" already exists", name)));
if (!dupSilent)//是否靜默警告資訊
ereport(WARNING,
(errcode(ERRCODE_DUPLICATE_CURSOR),
errmsg("closing existing cursor \"%s\"",
name)));
PortalDrop(portal, false);
}
/* make new portal structure */
//建立新的portal結構
portal = (Portal) MemoryContextAllocZero(TopPortalContext, sizeof *portal);
/* initialize portal context; typically it won't store much */
//初始化portal上下文,僅僅只是結構體,不存在資訊
portal->portalContext = AllocSetContextCreate(TopPortalContext,
"PortalContext",
ALLOCSET_SMALL_SIZES);
/* create a resource owner for the portal */
//建立resource owner
portal->resowner = ResourceOwnerCreate(CurTransactionResourceOwner,
"Portal");
/* initialize portal fields that don't start off zero */
//初始化portal中的域
portal->status = PORTAL_NEW;//狀態
portal->cleanup = PortalCleanup;//預設的cleanup函式
portal->createSubid = GetCurrentSubTransactionId();//正在建立的subxact
portal->activeSubid = portal->createSubid;//與createSubid一致
portal->strategy = PORTAL_MULTI_QUERY;//場景為PORTAL_MULTI_QUERY
portal->cursorOptions = CURSOR_OPT_NO_SCROLL;//預設為不允許滾動的遊標
portal->atStart = true;//處於開始
portal->atEnd = true; /* 預設不允許獲取資料;disallow fetches until query is set */
portal->visible = true;//在pg_cursors中可見
portal->creation_time = GetCurrentStatementStartTimestamp();//建立時間
/* put portal in table (sets portal->name) */
PortalHashTableInsert(portal, name);//放在HashTable中
/* reuse portal->name copy */
MemoryContextSetIdentifier(portal->portalContext, portal->name);//設定記憶體上下文標識
return portal;//返回portal結構體
}
PortalDefineQuery
PortalDefineQuery是構建portal's query資訊的一個簡單過程.
//------------------------------------------------------ PortalDefineQuery
/*
* PortalDefineQuery
* A simple subroutine to establish a portal's query.
* 構建portal's query的一個簡單過程.
*
* Notes: as of PG 8.4, caller MUST supply a sourceText string; it is not
* allowed anymore to pass NULL. (If you really don't have source text,
* you can pass a constant string, perhaps "(query not available)".)
* 注意:如為PG 8.4,呼叫者必須提供源文字,不允許為NULL.
* 如果沒有源文字,可以傳遞常量字串,比如"(query not available)"
*
* commandTag shall be NULL if and only if the original query string
* (before rewriting) was an empty string. Also, the passed commandTag must
* be a pointer to a constant string, since it is not copied.
* commandTag只有在原始查詢字串(重寫之前)為空字串時才為空。
* 另外,傳遞的commandTag必須是一個指向常量字串的指標,因為它不會被複制。
*
* If cplan is provided, then it is a cached plan containing the stmts, and
* the caller must have done GetCachedPlan(), causing a refcount increment.
* The refcount will be released when the portal is destroyed.
* 如果cplan不為NULL,那麼它就是一個包含stmts的快取計劃,呼叫者必須執行GetCachedPlan(),這會導致refcount的增加。
* 當門戶被銷燬時,refcount將被釋放。
*
* If cplan is NULL, then it is the caller's responsibility to ensure that
* the passed plan trees have adequate lifetime. Typically this is done by
* copying them into the portal's context.
* 如果cplan為空,那麼呼叫方有責任確保傳遞的計劃樹具有足夠長的生命週期。
* 通常,這是透過將它們複製到Portal的上下文中來完成的。
*
* The caller is also responsible for ensuring that the passed prepStmtName
* (if not NULL) and sourceText have adequate lifetime.
* 呼叫方同樣有責任確保傳遞的引數prepStmtName(如為NOT NULL)和sourceText有足夠長的生命期
*
* NB: this function mustn't do much beyond storing the passed values; in
* particular don't do anything that risks elog(ERROR). If that were to
* happen here before storing the cplan reference, we'd leak the plancache
* refcount that the caller is trying to hand off to us.
* 注意:這個函式除了儲存傳遞的值之外不會做什麼,特別是不做任何有可能出錯的事情。
* 如果在儲存cplan引用之前發生這種情況,會洩漏呼叫者試圖傳遞給我們的plancache refcount。
*/
void
PortalDefineQuery(Portal portal,
const char *prepStmtName,
const char *sourceText,
const char *commandTag,
List *stmts,
CachedPlan *cplan)
{
AssertArg(PortalIsValid(portal));
AssertState(portal->status == PORTAL_NEW);
AssertArg(sourceText != NULL);
AssertArg(commandTag != NULL || stmts == NIL);
//僅用於傳遞引數,給portal結構賦值
portal->prepStmtName = prepStmtName;
portal->sourceText = sourceText;
portal->commandTag = commandTag;
portal->stmts = stmts;
portal->cplan = cplan;
portal->status = PORTAL_DEFINED;//設定狀態為PORTAL_DEFINED
}
PortalSetResultFormat
PortalSetResultFormat函式為portal的輸出選擇格式化碼.
//------------------------------------------------------ PortalSetResultFormat
/*
* PortalSetResultFormat
* Select the format codes for a portal's output.
* 為portal的輸出選擇格式化碼.
*
* This must be run after PortalStart for a portal that will be read by
* a DestRemote or DestRemoteExecute destination. It is not presently needed
* for other destination types.
* 這必須在PortalStart呼叫之後執行,portal結構體將由DestRemote或DestRemoteExecute的目標地讀取。
* 其他目標型別目前不需要它。
*
* formats[] is the client format request, as per Bind message conventions.
* formats[]是客戶端的格式化請求,按照繫結的訊息約定.
*/
void
PortalSetResultFormat(Portal portal, int nFormats, int16 *formats)
{
int natts;
int i;
/* Do nothing if portal won't return tuples */
//如portal不返回元組,則直接返回
if (portal->tupDesc == NULL)
return;
natts = portal->tupDesc->natts;
portal->formats = (int16 *)
MemoryContextAlloc(portal->portalContext,
natts * sizeof(int16));
if (nFormats > 1)
{
/* format specified for each column */
//對每一列進行格式化
if (nFormats != natts)
ereport(ERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("bind message has %d result formats but query has %d columns",
nFormats, natts)));
memcpy(portal->formats, formats, natts * sizeof(int16));
}
else if (nFormats > 0)
{
/* single format specified, use for all columns */
//指定格式,用於所有列
int16 format1 = formats[0];
for (i = 0; i < natts; i++)
portal->formats[i] = format1;
}
else
{
/* use default format for all columns */
//所有列使用預設的格式
for (i = 0; i < natts; i++)
portal->formats[i] = 0;
}
}
PortalRun
PortalRun執行portal單個查詢或多個查詢
//------------------------------------------------------ PortalRun
/*
* PortalRun
* Run a portal's query or queries.
* 執行portal查詢或多個查詢
*
* 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". Note that count is ignored in multi-query
* situations, where we always run the portal to completion.
* count <= 0被解釋為no-op:目標啟動並關閉,但是沒有發生其他事情。
* count == FETCH_ALL被解釋為“所有行”。
* 注意,在多個查詢的情況下,count被忽略,在這種情況下,我們總是執行portal直到完成。
*
* isTopLevel: true if query is being executed at backend "top level"
* (that is, directly from a client command message)
* isTopLevel: T如果查詢將在後臺"top level"執行
*
* dest: where to send output of primary (canSetTag) query
* dest: 主查詢(canSetTag)將輸出到哪裡
*
* altdest: where to send output of non-primary queries
* altdest:非主查詢(non-primary)將輸出到哪裡
*
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
* in which to store a command completion status string.
* May be NULL if caller doesn't want a status string.
* completionTag:指向一個大小為COMPLETION_TAG_BUFSIZE的緩衝區,其中儲存一個命令完成狀態字串。
* 如果呼叫者不想要狀態字串,則可能為空。
*
* Returns true if the portal's execution is complete, false if it was
* suspended due to exhaustion of the count parameter.
* 如portal執行完成,返回T,否則如果由於計數引數耗盡而暫停,則為false
*/
bool
PortalRun(Portal portal, long count, bool isTopLevel, bool run_once,
DestReceiver *dest, DestReceiver *altdest,
char *completionTag)
{
bool result;
uint64 nprocessed;
ResourceOwner saveTopTransactionResourceOwner;
MemoryContext saveTopTransactionContext;
Portal saveActivePortal;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveMemoryContext;
AssertArg(PortalIsValid(portal));
TRACE_POSTGRESQL_QUERY_EXECUTE_START();
/* Initialize completion tag to empty string */
//初始化completionTag為空串
if (completionTag)
completionTag[0] = '\0';
if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY)
{
elog(DEBUG3, "PortalRun");
/* PORTAL_MULTI_QUERY logs its own stats per query */
ResetUsage();
}
/*
* Check for improper portal use, and mark portal active.
* 檢查portal是否使用得當,如OK則標記為活動。
*/
MarkPortalActive(portal);
/* Set run_once flag. Shouldn't be clear if previously set. */
//設定run_once標記,如果先前已設定,則不要清除此標記
Assert(!portal->run_once || run_once);
portal->run_once = run_once;
/*
* Set up global portal context pointers.
* 設定全域性portal上下文指標
*
* We have to play a special game here to support utility commands like
* VACUUM and CLUSTER, which internally start and commit transactions.
* When we are called to execute such a command, CurrentResourceOwner will
* be pointing to the TopTransactionResourceOwner --- which will be
* destroyed and replaced in the course of the internal commit and
* restart. So we need to be prepared to restore it as pointing to the
* exit-time TopTransactionResourceOwner. (Ain't that ugly? This idea of
* internally starting whole new transactions is not good.)
* CurrentMemoryContext has a similar problem, but the other pointers we
* save here will be NULL or pointing to longer-lived objects.
* 我們必須在這裡玩一個特殊的"把戲"來支援像VACUUM和CLUSTER這樣的實用命令,它們在內部啟動和提交事務。
* 當被呼叫執行這樣的命令時,CurrentResourceOwner將指向
* TopTransactionResourceOwner——它將在內部提交和重新啟動的過程中被銷燬和替換。
* 因此,我們需要準備將其恢復為指向exit-time TopTransactionResourceOwner。
* (這樣的做法很醜陋吧?這種內部啟動全新事務的想法其實是不好的。)
* CurrentMemoryContext也有類似的問題,但是在這裡儲存的其他指標將為NULL,或者指向生命週期更長的物件。
*/
//儲存"現場"
saveTopTransactionResourceOwner = TopTransactionResourceOwner;
saveTopTransactionContext = TopTransactionContext;
saveActivePortal = ActivePortal;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
saveMemoryContext = CurrentMemoryContext;
PG_TRY();
{
ActivePortal = portal;
if (portal->resowner)
CurrentResourceOwner = portal->resowner;
PortalContext = portal->portalContext;
MemoryContextSwitchTo(PortalContext);
switch (portal->strategy)//根據場景執行不同的邏輯
{
case PORTAL_ONE_SELECT:
case PORTAL_ONE_RETURNING:
case PORTAL_ONE_MOD_WITH:
case PORTAL_UTIL_SELECT:
/*
* If we have not yet run the command, do so, storing its
* results in the portal's tuplestore. But we don't do that
* for the PORTAL_ONE_SELECT case.
* 如果還沒有執行該命令,那麼就將其結果儲存在portal的tuplestore中。
* 但對於PORTAL_ONE_SELECT,則不會這樣做。
*/
if (portal->strategy != PORTAL_ONE_SELECT && !portal->holdStore)
FillPortalStore(portal, isTopLevel);
/*
* Now fetch desired portion of results.
* 現在開始獲取所需的部分結果-->執行PortalRunSelect。
*/
nprocessed = PortalRunSelect(portal, true, count, dest);
/*
* If the portal result contains a command tag and the caller
* gave us a pointer to store it, copy it. Patch the "SELECT"
* tag to also provide the rowcount.
* 如果portal結果包含一個命令標記,呼叫者將給我們一個指標來儲存,需要複製此標記。
* 修補“SELECT”標籤以提供行數。
*/
if (completionTag && portal->commandTag)
{
if (strcmp(portal->commandTag, "SELECT") == 0)
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
"SELECT " UINT64_FORMAT, nprocessed);
else
strcpy(completionTag, portal->commandTag);
}
/* Mark portal not active */
//標記portal為PORTAL_READY
portal->status = PORTAL_READY;
/*
* Since it's a forward fetch, say DONE iff atEnd is now true.
* 由於這是前向獲取,設定result為atEnd
*/
result = portal->atEnd;
break;
case PORTAL_MULTI_QUERY:
PortalRunMulti(portal, isTopLevel, false,
dest, altdest, completionTag);
/* Prevent portal's commands from being re-executed */
//防止portal命令重複執行
MarkPortalDone(portal);
/* Always complete at end of RunMulti */
//在RunMulti最後設定result為T
result = true;
break;
default://錯誤的場景
elog(ERROR, "unrecognized portal strategy: %d",
(int) portal->strategy);
result = false; /* 讓編譯器"閉嘴";keep compiler quiet */
break;
}
}
PG_CATCH();
{
/* Uncaught error while executing portal: mark it dead */
//未捕獲的錯誤,設定portal狀態為dead
MarkPortalFailed(portal);
/* Restore global vars and propagate error */
//恢復全域性的vars並丟擲錯誤
if (saveMemoryContext == saveTopTransactionContext)
MemoryContextSwitchTo(TopTransactionContext);
else
MemoryContextSwitchTo(saveMemoryContext);
ActivePortal = saveActivePortal;
if (saveResourceOwner == saveTopTransactionResourceOwner)
CurrentResourceOwner = TopTransactionResourceOwner;
else
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
PG_RE_THROW();
}
PG_END_TRY();
if (saveMemoryContext == saveTopTransactionContext)
MemoryContextSwitchTo(TopTransactionContext);
else
MemoryContextSwitchTo(saveMemoryContext);
ActivePortal = saveActivePortal;
if (saveResourceOwner == saveTopTransactionResourceOwner)
CurrentResourceOwner = TopTransactionResourceOwner;
else
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY)
ShowUsage("EXECUTOR STATISTICS");
TRACE_POSTGRESQL_QUERY_EXECUTE_DONE();
return result;
}
PortalDrop
PortalDrop函式銷燬portal結構體
//------------------------------------------------------ PortalDrop
/*
* PortalDrop
* Destroy the portal.
* 銷燬portal結構體
*/
void
PortalDrop(Portal portal, bool isTopCommit)
{
AssertArg(PortalIsValid(portal));
/*
* Don't allow dropping a pinned portal, it's still needed by whoever
* pinned it.
* 不允許清除pinned portal,在某些地方還需要
*/
if (portal->portalPinned)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cannot drop pinned portal \"%s\"", portal->name)));
/*
* Not sure if the PORTAL_ACTIVE case can validly happen or not...
* 不確定PORTAL_ACTIVE這種場景是否能有效發生…
*/
if (portal->status == PORTAL_ACTIVE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
errmsg("cannot drop active portal \"%s\"", portal->name)));
/*
* Allow portalcmds.c to clean up the state it knows about, in particular
* shutting down the executor if still active. This step potentially runs
* user-defined code so failure has to be expected. It's the cleanup
* hook's responsibility to not try to do that more than once, in the case
* that failure occurs and then we come back to drop the portal again
* during transaction abort.
* 允許portalcmds.c清理相關狀態,特別是關閉執行器(如果執行器仍然活躍)。
* 這個步驟可能執行使用者自定義的程式碼,因此必須預料到會可能出現故障。
* 在發生故障時,清理鉤子的責任是不要嘗試多次這樣做,然後在事務中止期間再次刪除portal。
*
* Note: in most paths of control, this will have been done already in
* MarkPortalDone or MarkPortalFailed. We're just making sure.
* 注意:在大多數控制路徑中,這將在MarkPortalDone或MarkPortalFailed中完成。但需要確認。
*
*/
if (PointerIsValid(portal->cleanup))
{
portal->cleanup(portal);
portal->cleanup = NULL;
}
/*
* Remove portal from hash table. Because we do this here, we will not
* come back to try to remove the portal again if there's any error in the
* subsequent steps. Better to leak a little memory than to get into an
* infinite error-recovery loop.
* 從雜湊表中刪除portal。
* 因為在這裡這樣做,所以如果後續步驟中出現任何錯誤,將不再試圖再次刪除portal。
* 洩漏一點記憶體總比陷入無限的錯誤恢復迴圈要好。
*/
PortalHashTableDelete(portal);
/* drop cached plan reference, if any */
//清除已快取的plan引用
PortalReleaseCachedPlan(portal);
/*
* If portal has a snapshot protecting its data, release that. This needs
* a little care since the registration will be attached to the portal's
* resowner; if the portal failed, we will already have released the
* resowner (and the snapshot) during transaction abort.
* 如果portal有一個保護其資料的快照,那麼釋放它。
* 這需要注意一點,因為註冊器將附加到portal的resowner;
* 如果portal執行失敗,將在事務中止期間釋放resowner(和快照)。
*/
if (portal->holdSnapshot)
{
if (portal->resowner)
UnregisterSnapshotFromOwner(portal->holdSnapshot,
portal->resowner);
portal->holdSnapshot = NULL;
}
/*
* Release any resources still attached to the portal. There are several
* cases being covered here:
* 釋放仍附加到portal的所有資源。這裡涉及幾種情況:
*
* Top transaction commit (indicated by isTopCommit): normally we should
* do nothing here and let the regular end-of-transaction resource
* releasing mechanism handle these resources too. However, if we have a
* FAILED portal (eg, a cursor that got an error), we'd better clean up
* its resources to avoid resource-leakage warning messages.
* Top事務提交(由isTopCommit表示):通常在這裡什麼也不做,讓常規的事務結束資源釋放機制也處理這些資源。
* 但是,如果有一個失敗的portal(例如,遊標出錯),那麼最好清理它的資源,以避免資源洩漏警告訊息。
*
* Sub transaction commit: never comes here at all, since we don't kill
* any portals in AtSubCommit_Portals().
* 子事務提交:永遠不會出現在這裡,因為不會殺死atsubcommit_ports()中的任何portal。
*
* Main or sub transaction abort: we will do nothing here because
* portal->resowner was already set NULL; the resources were already
* cleaned up in transaction abort.
* 主事務或子事務中止:什麼也不做,因為portal->resowner已經設定為NULL;事務中止中已經清理了資源。
*
* Ordinary portal drop: must release resources. However, if the portal
* is not FAILED then we do not release its locks. The locks become the
* responsibility of the transaction's ResourceOwner (since it is the
* parent of the portal's owner) and will be released when the transaction
* eventually ends.
* 普通portal清除:必須釋放資源。
* 然而,如果portal沒有失敗,那麼不會釋放它的鎖。
* 鎖由事務的ResourceOwner負責(因為它是portal所有者的父類),並在事務最終結束時被釋放。
*/
if (portal->resowner &&
(!isTopCommit || portal->status == PORTAL_FAILED))
{
bool isCommit = (portal->status != PORTAL_FAILED);
ResourceOwnerRelease(portal->resowner,
RESOURCE_RELEASE_BEFORE_LOCKS,
isCommit, false);
ResourceOwnerRelease(portal->resowner,
RESOURCE_RELEASE_LOCKS,
isCommit, false);
ResourceOwnerRelease(portal->resowner,
RESOURCE_RELEASE_AFTER_LOCKS,
isCommit, false);
ResourceOwnerDelete(portal->resowner);
}
portal->resowner = NULL;
/*
* Delete tuplestore if present. We should do this even under error
* conditions; since the tuplestore would have been using cross-
* transaction storage, its temp files need to be explicitly deleted.
* 如果存在,刪除tuplestore。
* 即使在錯誤的情況下,也應該這樣做;由於tuplestore將一直使用跨事務儲存,因此需要顯式刪除其臨時檔案。
*/
if (portal->holdStore)
{
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(portal->holdContext);
tuplestore_end(portal->holdStore);
MemoryContextSwitchTo(oldcontext);
portal->holdStore = NULL;
}
/* delete tuplestore storage, if any */
//刪除tuplestore儲存
if (portal->holdContext)
MemoryContextDelete(portal->holdContext);
/* release subsidiary storage */
//釋放portalContext儲存
MemoryContextDelete(portal->portalContext);
/* release portal struct (it's in TopPortalContext) */
//釋放portal結構體(在TopPortalContext中)
pfree(portal);
}
三、跟蹤分析
測試指令碼如下
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,設定斷點,進入exec_simple_query
(gdb) b exec_simple_query
Breakpoint 1 at 0x8c59af: file postgres.c, line 893.
(gdb) c
Continuing.
Breakpoint 1, exec_simple_query (
query_string=0x2a9eeb8 "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>...) at postgres.c:893
893 CommandDest dest = whereToSendOutput;
(gdb)
進入CreatePortal
1058 CHECK_FOR_INTERRUPTS();
(gdb)
1064 portal = CreatePortal("", true, true);
(gdb) step
CreatePortal (name=0xc5b7d8 "", allowDup=true, dupSilent=true) at portalmem.c:179
179 AssertArg(PointerIsValid(name));
CreatePortal-->設定portal的相關資訊
216 portal->atEnd = true; /* disallow fetches until query is set */
(gdb)
217 portal->visible = true;
(gdb)
218 portal->creation_time = GetCurrentStatementStartTimestamp();
(gdb)
221 PortalHashTableInsert(portal, name);
(gdb)
224 MemoryContextSetIdentifier(portal->portalContext, portal->name);
(gdb)
226 return portal;
CreatePortal-->檢視portal結構體
(gdb) p *portal
$1 = {name = 0x2b07e90 "", prepStmtName = 0x0, portalContext = 0x2b8b7a0, resowner = 0x2acfe80,
cleanup = 0x6711b6 <PortalCleanup>, createSubid = 1, activeSubid = 1, sourceText = 0x0, commandTag = 0x0, stmts = 0x0,
cplan = 0x0, portalParams = 0x0, queryEnv = 0x0, strategy = PORTAL_MULTI_QUERY, cursorOptions = 4, run_once = false,
status = PORTAL_NEW, portalPinned = false, autoHeld = false, queryDesc = 0x0, tupDesc = 0x0, formats = 0x0,
holdStore = 0x0, holdContext = 0x0, holdSnapshot = 0x0, atStart = true, atEnd = true, portalPos = 0,
creation_time = 595049454962775, visible = true}
回到exec_simple_query
(gdb)
exec_simple_query (
query_string=0x2a9eeb8 "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>...) at postgres.c:1066
1066 portal->visible = false;
進入PortalDefineQuery
(gdb)
1073 PortalDefineQuery(portal,
(gdb) step
PortalDefineQuery (portal=0x2b04468, prepStmtName=0x0,
sourceText=0x2a9eeb8 "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=0x2b86800, cplan=0x0) at portalmem.c:288
288 AssertArg(PortalIsValid(portal));
PortalDefineQuery-->設定相關引數
294 portal->prepStmtName = prepStmtName;
(gdb)
295 portal->sourceText = sourceText;
(gdb)
296 portal->commandTag = commandTag;
(gdb)
297 portal->stmts = stmts;
(gdb)
298 portal->cplan = cplan;
(gdb)
299 portal->status = PORTAL_DEFINED;
(gdb)
300 }
PortalDefineQuery-->檢視portal結構體
(gdb) p *portal
$2 = {name = 0x2b07e90 "", prepStmtName = 0x0, portalContext = 0x2b8b7a0, resowner = 0x2acfe80,
cleanup = 0x6711b6 <PortalCleanup>, createSubid = 1, activeSubid = 1,
sourceText = 0x2a9eeb8 "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 = 0x2b86800, cplan = 0x0, portalParams = 0x0, queryEnv = 0x0,
strategy = PORTAL_MULTI_QUERY, cursorOptions = 4, run_once = false, status = PORTAL_DEFINED, portalPinned = false,
autoHeld = false, queryDesc = 0x0, tupDesc = 0x0, formats = 0x0, holdStore = 0x0, holdContext = 0x0, holdSnapshot = 0x0,
atStart = true, atEnd = true, portalPos = 0, creation_time = 595049454962775, visible = false}
回到exec_simple_query,進入PortalSetResultFormat
(gdb)
1105 PortalSetResultFormat(portal, 1, &format);
(gdb) step
PortalSetResultFormat (portal=0x2b04468, nFormats=1, formats=0x7ffff7153cbe) at pquery.c:633
633 if (portal->tupDesc == NULL)
PortalSetResultFormat-->需返回元組,nFormats為1
...
(gdb) p *portal->tupDesc
$4 = {natts = 7, tdtypeid = 2249, tdtypmod = -1, tdhasoid = false, tdrefcount = -1, constr = 0x0, attrs = 0x2b989c8}
(gdb)
(gdb) p nFormats
$5 = 1
PortalSetResultFormat-->格式碼為0
(gdb) p *portal->formats
$7 = 0
回到exec_simple_query,進入PortalRun
(gdb)
1122 (void) PortalRun(portal,
(gdb) step
PortalRun (portal=0x2b04468, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x2b86838, altdest=0x2b86838,
completionTag=0x7ffff7153c70 ":\001") at pquery.c:702
702 AssertArg(PortalIsValid(portal));
PortalRun-->初始化completionTag為空串
707 if (completionTag)
(gdb)
708 completionTag[0] = '\0';
(gdb) p *completionTag
$12 = 0 '\000'
PortalRun-->設定狀態為active等
(gdb) p portal->status
$15 = PORTAL_ACTIVE
(gdb) p portal->run_once
$16 = true
PortalRun-->保護"現場"
(gdb) n
741 saveTopTransactionContext = TopTransactionContext;
(gdb)
742 saveActivePortal = ActivePortal;
(gdb)
743 saveResourceOwner = CurrentResourceOwner;
(gdb)
744 savePortalContext = PortalContext;
(gdb)
745 saveMemoryContext = CurrentMemoryContext;
PortalRun-->開始執行
(gdb)
746 PG_TRY();
PortalRun-->根據場景呼叫相應的函式,在這裡是PortalRunSelect
...
(gdb)
755 switch (portal->strategy)
(gdb)
767 if (portal->strategy != PORTAL_ONE_SELECT && !portal->holdStore)
(gdb) n
773 nprocessed = PortalRunSelect(portal, true, count, dest);
PortalRun-->處理行數的計數
(gdb) p nprocessed
$17 = 99991
設定命令完成標記
(gdb) n
782 if (strcmp(portal->commandTag, "SELECT") == 0)
(gdb)
783 snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
設定portal狀態為PORTAL_READY,結果為T
(gdb)
790 portal->status = PORTAL_READY;
(gdb) p portal->status
$18 = PORTAL_ACTIVE
(gdb) n
795 result = portal->atEnd;
(gdb)
796 break;
(gdb) p result
$19 = true
恢復"現場",返回結果
...
846 PortalContext = savePortalContext;
(gdb)
848 if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY)
(gdb)
851 TRACE_POSTGRESQL_QUERY_EXECUTE_DONE();
(gdb)
853 return result;
(gdb)
854 }
回到exec_simple_query,進入PortalDrop
(gdb) n
exec_simple_query (
query_string=0x2a9eeb8 "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>...) at postgres.c:1130
1130 receiver->rDestroy(receiver);
(gdb)
1132 PortalDrop(portal, false);
PortalDrop-->釋放資源
...
(gdb)
589 MemoryContextDelete(portal->portalContext);
(gdb)
592 pfree(portal);
(gdb)
593 }
DONE!
四、參考資料
postgres.c
PG Document:Query Planning
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/6906/viewspace-2374808/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- PostgreSQL 原始碼解讀(67)- 查詢語句#52(make_one_rel函式#17-...SQL原始碼函式
- PostgreSQL 原始碼解讀(83)- 查詢語句#68(PortalStart函式)SQL原始碼函式
- PostgreSQL 原始碼解讀(90)- 查詢語句#75(ExecHashJoin函式#1)SQL原始碼函式
- PostgreSQL 原始碼解讀(91)- 查詢語句#76(ExecHashJoin函式#2)SQL原始碼函式
- PostgreSQL 原始碼解讀(93)- 查詢語句#77(ExecHashJoin函式#3)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原始碼函式
- PostgreSQL 原始碼解讀(38)- 查詢語句#23(query_planner函式#1)SQL原始碼函式
- PostgreSQL 原始碼解讀(39)- 查詢語句#24(query_planner函式#2)SQL原始碼函式
- PostgreSQL 原始碼解讀(73)- 查詢語句#58(grouping_planner函式...SQL原始碼函式
- PostgreSQL 原始碼解讀(24)- 查詢語句#9(查詢重寫)SQL原始碼
- PostgreSQL 原始碼解讀(78)- 查詢語句#63(create_plan函式#2-cr...SQL原始碼函式
- PostgreSQL 原始碼解讀(79)- 查詢語句#64(create_plan函式#3-Se...SQL原始碼函式
- PostgreSQL 原始碼解讀(80)- 查詢語句#65(create_plan函式#4-Jo...SQL原始碼函式
- PostgreSQL 原始碼解讀(65)- 查詢語句#50(make_one_rel函式#15-...SQL原始碼函式
- PostgreSQL 原始碼解讀(62)- 查詢語句#47(make_one_rel函式#12-...SQL原始碼函式
- PostgreSQL 原始碼解讀(63)- 查詢語句#48(make_one_rel函式#13-...SQL原始碼函式
- PostgreSQL 原始碼解讀(64)- 查詢語句#49(make_one_rel函式#14-...SQL原始碼函式
- PostgreSQL 原始碼解讀(60)- 查詢語句#45(make_one_rel函式#10-...SQL原始碼函式
- PostgreSQL 原始碼解讀(61)- 查詢語句#46(make_one_rel函式#11-...SQL原始碼函式
- PostgreSQL 原始碼解讀(69)- 查詢語句#54(make_one_rel函式#19-...SQL原始碼函式
- PostgreSQL 原始碼解讀(70)- 查詢語句#55(make_one_rel函式#20-...SQL原始碼函式
- PostgreSQL 原始碼解讀(66)- 查詢語句#51(make_one_rel函式#16-...SQL原始碼函式
- PostgreSQL 原始碼解讀(68)- 查詢語句#53(make_one_rel函式#18-...SQL原始碼函式
- PostgreSQL 原始碼解讀(71)- 查詢語句#56(make_one_rel函式#21-...SQL原始碼函式
- PostgreSQL 原始碼解讀(72)- 查詢語句#57(make_one_rel函式#22-...SQL原始碼函式
- PostgreSQL 原始碼解讀(20)- 查詢語句#5(查詢樹Query詳解)SQL原始碼
- PostgreSQL 原始碼解讀(18)- 查詢語句#3(SQL Parse)SQL原始碼
- PostgreSQL 原始碼解讀(19)- 查詢語句#4(ParseTree詳解)SQL原始碼