溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PostgreSQL 源碼解讀(82)- 查詢語句#67(PortalXXX系列函數(shù))

發(fā)布時間:2020-08-07 02:48:41 來源:ITPUB博客 閱讀:361 作者:husthxd 欄目:關(guān)系型數(shù)據(jù)庫

本節(jié)介紹了PortalXXX函數(shù),這些函數(shù)在create_simple_query中被調(diào)用,包括CreatePortal、PortalDefineQuery、PortalSetResultFormat、PortalRun和PortalDrop函數(shù)。

一、數(shù)據(jù)結(jié)構(gòu)

Portal
包括場景PortalStrategy枚舉定義/PortalStatus狀態(tài)定義/PortalData結(jié)構(gòu)體.Portal是PortalData結(jié)構(gòu)體指針,詳見代碼注釋.

/*
 * 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(客戶端請求),有幾種執(zhí)行策略,具體取決于要執(zhí)行什么查詢。
 * (注意:無論什么情況下,一個Portal只執(zhí)行一個source-SQL查詢,因此從用戶的角度來看只產(chǎn)生一個結(jié)果。
 * 但是,規(guī)則重寫器可以將單個源查詢擴展為零或多個實際查詢。
 * 
 * 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查詢。
 *                    按需要的結(jié)果重復(遞增)地運行執(zhí)行器。
 *                    該策略還支持可持有游標(執(zhí)行器結(jié)果可以在事務完成后轉(zhuǎn)儲到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查詢
                         (可能還包括由規(guī)則重寫添加的輔助查詢)。
 *                       在第一次執(zhí)行時,運行Portal來完成并將主查詢的結(jié)果轉(zhuǎn)儲到Portal的tuplestore中;
 *                       然后根據(jù)需要將結(jié)果返回給客戶端。
 *                       (我們不能支持半途中斷的查詢,因為AFTER觸發(fā)器代碼無法處理,
 *                       也因為不想冒執(zhí)行所有輔助查詢失敗的風險)。
 * 
 * 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查詢,但它具有數(shù)據(jù)修改的CTEs。
 *                      這與PORTAL_ONE_RETURNING的情況相同,因為可能需要觸發(fā)觸發(fā)器。將來它的行為可能更像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的結(jié)果(例如,EXPLAIN或SHOW)。
 *                     在第一次執(zhí)行時,運行語句并將其結(jié)果轉(zhuǎn)儲到portal tuplestore;然后根據(jù)需要將結(jié)果返回給客戶端。
 * 
 * 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: 除上述情況外的其他情況。
 *                     在這里,不支持部分執(zhí)行:Portal的查詢語句將在第一次調(diào)用時運行到完成。
 */
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總是處于這些狀態(tài)中的之一。
 * 如果查詢沒有運行到完成,則可以從活動狀態(tài)轉(zhuǎn)回準備狀態(tài);否則永遠不會后退。
 */
typedef enum PortalStatus
{
    PORTAL_NEW,                 /* 剛創(chuàng)建;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已經(jīng)完成;portal is finished (don't re-run it) */
    PORTAL_FAILED               /* Portal出現(xiàn)錯誤;portal got error (can't re-run it) */
} PortalStatus;

typedef struct PortalData *Portal;//結(jié)構(gòu)體指針

typedef struct PortalData
{
    /* Bookkeeping data */
    const char *name;           /* portal的名稱;portal's name */
    const char *prepStmtName;   /* 已完成準備的源語句;source prepared statement (NULL if none) */
    MemoryContext portalContext;    /* 內(nèi)存上下文;subsidiary memory for portal */
    ResourceOwner resowner;     /* 資源的owner;resources owned by portal */
    void        (*cleanup) (Portal portal); /* cleanup鉤子函數(shù);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.
     * 狀態(tài)數(shù)據(jù),用于記住在哪個子事務中創(chuàng)建或使用Portal。
     * 如果Portal是從以前的事務中持有的,那么兩個subxids都應該是InvalidSubTransactionId。
     * 否則,createSubid是正在創(chuàng)建的subxact,而activeSubid是運行Portal的最后一個subxact。
     */
    SubTransactionId createSubid;   /* 正在創(chuàng)建的subxact;the creating subxact */
    SubTransactionId activeSubid;   /* 活動的最后一個subxact;the last subxact with activity */

    /* The query or queries the portal will execute */
    //portal將會執(zhí)行的查詢
    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; /* 傳遞給查詢的參數(shù);params to pass to query */
    QueryEnvironment *queryEnv; /* 查詢的執(zhí)行環(huán)境;environment for query */

    /* Features/options */
    PortalStrategy strategy;    /* 場景;see above */
    int         cursorOptions;  /* DECLARE CURSOR選項位;DECLARE CURSOR option bits */
    bool        run_once;       /* 是否只執(zhí)行一次;portal will only be run once */

    /* Status data */
    PortalStatus status;        /* Portal的狀態(tài);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,執(zhí)行器處于活動狀態(tài)
    QueryDesc  *queryDesc;      /* 執(zhí)行器需要使用的信息;info needed for executor invocation */

    /* If portal returns tuples, this is their tupdesc: */
    //如Portal需要返回元組,這是元組的描述
    TupleDesc   tupDesc;        /* 結(jié)果元組的描述;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存儲元組。
     * (在事務結(jié)束后持有的游標不再具有任何活動執(zhí)行器狀態(tài)。)
     */
    Tuplestorestate *holdStore; /* 存儲持有的游標信息;store for holdable cursors */
    MemoryContext holdContext;  /* 持有holdStore的內(nèi)存上下文;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數(shù)據(jù)一起被清除。
     * 對于持有的游標,通過強制解壓數(shù)據(jù)來避免需要保留這樣的快照。
     */
    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。
     * 在運行結(jié)束后,portalPos = #查詢中的行號,atEnd為T。
     * 注意,atStart表示portalPos == 0,但不是相反:我們可能只回到到第一行,而不是開始。
     * 還要注意,各種代碼在開始和結(jié)束時都要檢查,但是只有Portal移動例程應該訪問portalPos。
     */
    bool        atStart;//處于開始位置?
    bool        atEnd;//處于結(jié)束位置?
    uint64      portalPos;//實際行號

    /* Presentation data, primarily used by the pg_cursors system view */
    //用于表示的數(shù)據(jù),主要由pg_cursors系統(tǒng)視圖使用
    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函數(shù)創(chuàng)建給定名稱的Portal結(jié)構(gòu).

//------------------------------------------------------ CreatePortal
/*
 * CreatePortal
 *      Returns a new portal given a name.
 *      創(chuàng)建給定名稱的Portal結(jié)構(gòu)
 *
 * 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));
    //根據(jù)給定的名稱獲取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 */
    //創(chuàng)建新的portal結(jié)構(gòu)
    portal = (Portal) MemoryContextAllocZero(TopPortalContext, sizeof *portal);

    /* initialize portal context; typically it won't store much */
    //初始化portal上下文,僅僅只是結(jié)構(gòu)體,不存在信息
    portal->portalContext = AllocSetContextCreate(TopPortalContext,
                                                  "PortalContext",
                                                  ALLOCSET_SMALL_SIZES);

    /* create a resource owner for the portal */
    //創(chuàng)建resource owner
    portal->resowner = ResourceOwnerCreate(CurTransactionResourceOwner,
                                           "Portal");

    /* initialize portal fields that don't start off zero */
    //初始化portal中的域
    portal->status = PORTAL_NEW;//狀態(tài)
    portal->cleanup = PortalCleanup;//默認的cleanup函數(shù)
    portal->createSubid = GetCurrentSubTransactionId();//正在創(chuàng)建的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;       /* 默認不允許獲取數(shù)據(jù);disallow fetches until query is set */
    portal->visible = true;//在pg_cursors中可見
    portal->creation_time = GetCurrentStatementStartTimestamp();//創(chuàng)建時間

    /* put portal in table (sets portal->name) */
    PortalHashTableInsert(portal, name);//放在HashTable中

    /* reuse portal->name copy */
    MemoryContextSetIdentifier(portal->portalContext, portal->name);//設置內(nèi)存上下文標識

    return portal;//返回portal結(jié)構(gòu)體
}

PortalDefineQuery
PortalDefineQuery是構(gòu)建portal's query信息的一個簡單過程.


//------------------------------------------------------ PortalDefineQuery

/*
 * PortalDefineQuery
 *      A simple subroutine to establish a portal's query.
 *      構(gòu)建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,調(diào)用者必須提供源文本,不允許為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的緩存計劃,調(diào)用者必須執(zhí)行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為空,那么調(diào)用方有責任確保傳遞的計劃樹具有足夠長的生命周期。
 * 通常,這是通過將它們復制到Portal的上下文中來完成的。
 *
 * The caller is also responsible for ensuring that the passed prepStmtName
 * (if not NULL) and sourceText have adequate lifetime.
 * 調(diào)用方同樣有責任確保傳遞的參數(shù)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.
 * 注意:這個函數(shù)除了存儲傳遞的值之外不會做什么,特別是不做任何有可能出錯的事情。
 * 如果在存儲cplan引用之前發(fā)生這種情況,會泄漏調(diào)用者試圖傳遞給我們的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);
    //僅用于傳遞參數(shù),給portal結(jié)構(gòu)賦值
    portal->prepStmtName = prepStmtName;
    portal->sourceText = sourceText;
    portal->commandTag = commandTag;
    portal->stmts = stmts;
    portal->cplan = cplan;
    portal->status = PORTAL_DEFINED;//設置狀態(tài)為PORTAL_DEFINED
}

PortalSetResultFormat
PortalSetResultFormat函數(shù)為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調(diào)用之后運行,portal結(jié)構(gòu)體將由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執(zhí)行portal單個查詢或多個查詢


//------------------------------------------------------ PortalRun

/*
 * PortalRun
 *      Run a portal's query or queries.
 *      執(zhí)行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:目標啟動并關(guān)閉,但是沒有發(fā)生其他事情。
 * 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"執(zhí)行
 *
 * 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的緩沖區(qū),其中存儲一個命令完成狀態(tài)字符串。
 * 如果調(diào)用者不想要狀態(tài)字符串,則可能為空。
 * 
 * Returns true if the portal's execution is complete, false if it was
 * suspended due to exhaustion of the count parameter.
 * 如portal執(zhí)行完成,返回T,否則如果由于計數(shù)參數(shù)耗盡而暫停,則為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這樣的實用命令,它們在內(nèi)部啟動和提交事務。
     * 當被調(diào)用執(zhí)行這樣的命令時,CurrentResourceOwner將指向
     * TopTransactionResourceOwner——它將在內(nèi)部提交和重新啟動的過程中被銷毀和替換。
     * 因此,我們需要準備將其恢復為指向exit-time TopTransactionResourceOwner。
     * (這樣的做法很丑陋吧?這種內(nèi)部啟動全新事務的想法其實是不好的。)
     * CurrentMemoryContext也有類似的問題,但是在這里保存的其他指針將為NULL,或者指向生命周期更長的對象。
     */
    //保存"現(xiàn)場"
    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)//根據(jù)場景執(zhí)行不同的邏輯
        {
            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.
                 * 如果還沒有運行該命令,那么就將其結(jié)果存儲在portal的tuplestore中。
                 * 但對于PORTAL_ONE_SELECT,則不會這樣做。
                 */
                if (portal->strategy != PORTAL_ONE_SELECT && !portal->holdStore)
                    FillPortalStore(portal, isTopLevel);

                /*
                 * Now fetch desired portion of results.
                 * 現(xiàn)在開始獲取所需的部分結(jié)果-->執(zhí)行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結(jié)果包含一個命令標記,調(diào)用者將給我們一個指針來存儲,需要復制此標記。
                 * 修補“SELECT”標簽以提供行數(shù)。
                 */
                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命令重復執(zhí)行
                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狀態(tài)為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函數(shù)銷毀portal結(jié)構(gòu)體


//------------------------------------------------------ PortalDrop

/*
 * PortalDrop
 *      Destroy the portal.
 *      銷毀portal結(jié)構(gòu)體
 */
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這種場景是否能有效發(fā)生…
     */
    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清理相關(guān)狀態(tài),特別是關(guān)閉執(zhí)行器(如果執(zhí)行器仍然活躍)。
     * 這個步驟可能運行用戶自定義的代碼,因此必須預料到會可能出現(xiàn)故障。
     * 在發(fā)生故障時,清理鉤子的責任是不要嘗試多次這樣做,然后在事務中止期間再次刪除portal。
     *
     * Note: in most paths of control, this will have been done already in
     * MarkPortalDone or MarkPortalFailed.  We're just making sure.
     * 注意:在大多數(shù)控制路徑中,這將在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。
     * 因為在這里這樣做,所以如果后續(xù)步驟中出現(xiàn)任何錯誤,將不再試圖再次刪除portal。
     * 泄漏一點內(nèi)存總比陷入無限的錯誤恢復循環(huán)要好。
     */
    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有一個保護其數(shù)據(jù)的快照,那么釋放它。
     * 這需要注意一點,因為注冊器將附加到portal的resowner;
     * 如果portal執(zhí)行失敗,將在事務中止期間釋放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表示):通常在這里什么也不做,讓常規(guī)的事務結(jié)束資源釋放機制也處理這些資源。
     * 但是,如果有一個失敗的portal(例如,游標出錯),那么最好清理它的資源,以避免資源泄漏警告消息。
     * 
     * Sub transaction commit: never comes here at all, since we don't kill
     * any portals in AtSubCommit_Portals().
     * 子事務提交:永遠不會出現(xiàn)在這里,因為不會殺死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已經(jīng)設置為NULL;事務中止中已經(jīng)清理了資源。
     * 
     * 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所有者的父類),并在事務最終結(jié)束時被釋放。
     */
    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結(jié)構(gòu)體(在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的相關(guān)信息

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結(jié)構(gòu)體

(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-->設置相關(guān)參數(shù)

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結(jié)構(gòu)體

(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-->設置狀態(tài)為active等

(gdb) p portal->status
$15 = PORTAL_ACTIVE
(gdb) p portal->run_once
$16 = true

PortalRun-->保護"現(xiàn)場"

(gdb) n
741     saveTopTransactionContext = TopTransactionContext;
(gdb) 
742     saveActivePortal = ActivePortal;
(gdb) 
743     saveResourceOwner = CurrentResourceOwner;
(gdb) 
744     savePortalContext = PortalContext;
(gdb) 
745     saveMemoryContext = CurrentMemoryContext;

PortalRun-->開始執(zhí)行

(gdb) 
746     PG_TRY();

PortalRun-->根據(jù)場景調(diào)用相應的函數(shù),在這里是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-->處理行數(shù)的計數(shù)

(gdb) p nprocessed
$17 = 99991

設置命令完成標記

(gdb) n
782                     if (strcmp(portal->commandTag, "SELECT") == 0)
(gdb) 
783                         snprintf(completionTag, COMPLETION_TAG_BUFSIZE,

設置portal狀態(tài)為PORTAL_READY,結(jié)果為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

恢復"現(xiàn)場",返回結(jié)果

...
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

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI