時(shí)光荏苒,歲月如梭。樓主已經(jīng)很久沒(méi)有更新了。之前說(shuō)好的一周一更的沒(méi)有做到。實(shí)在是事出有因,沒(méi)能靜下心來(lái)好好看代碼。當(dāng)然這不能作為我不更新的理由,時(shí)間擠擠還是有的,拖了這么久,該再寫點(diǎn)東西了,不然人就怠懶了。不過(guò)這回,我準(zhǔn)備寫的精簡(jiǎn)些,一方面我想偷點(diǎn)懶省點(diǎn)時(shí)間,二來(lái)畢竟寫太長(zhǎng)大家也不一定愛看。

之前我說(shuō)過(guò)的查詢分析,查詢重寫和查詢規(guī)劃都是相當(dāng)于是對(duì)查詢的"編譯"。那么編譯完了就應(yīng)該按照既定的策略去執(zhí)行它。本篇就來(lái)介紹查詢執(zhí)行模塊的代碼(Executor),歡迎拍磚。

這部分我主要從以下五個(gè)部分介紹查詢執(zhí)行模塊(很可能要分成四到五篇文章來(lái)闡述,畢竟是比查詢規(guī)劃還要復(fù)雜的模塊):

1.查詢優(yōu)化策略
2.非可優(yōu)化語(yǔ)句的執(zhí)行
3.可優(yōu)化語(yǔ)句的執(zhí)行
4.計(jì)劃節(jié)點(diǎn)
5.其它子功能介紹

查詢執(zhí)行器的框架結(jié)構(gòu)如下圖所示。
大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

在exec_simple_query函數(shù)中,調(diào)用查詢編譯模塊之后,就進(jìn)入了查詢執(zhí)行器模塊。在該模塊中,就是按照前一階段查詢規(guī)劃模塊鎖生成的查詢計(jì)劃,有機(jī)第調(diào)用存儲(chǔ)、索引,并發(fā)等模塊來(lái)完成數(shù)據(jù)的讀取或者修改的過(guò)程。

在本模塊中,總共下屬四個(gè)子模塊,分別是:Portal、ProcessUtility、Executor和其他特定子功能模塊。

我們知道,查詢規(guī)劃階段將查詢分為兩種類型,在查詢執(zhí)行模塊中,先由Portal模塊識(shí)別查詢類型(有計(jì)劃樹和無(wú)計(jì)劃樹),根據(jù)查詢類型分別指派Executor模塊和ProcessUtility模塊進(jìn)行處理。

這兩個(gè)子模塊的處理邏輯相差很大,執(zhí)行過(guò)程和先關(guān)數(shù)據(jù)結(jié)構(gòu)差異也很大。

對(duì)于Executor模塊,它根據(jù)輸入的查詢計(jì)劃樹按部就班地處理數(shù)據(jù)表中元組的增刪改查(DML)操作,它的執(zhí)行邏輯是統(tǒng)一的(所有的增刪改查最后都?xì)w結(jié)為SELECT,只是分別在SELECT的基礎(chǔ)上進(jìn)行一些額外的操作)。其主要代碼放在src/backend/executor下。

而對(duì)于ProcessUtility模塊,由于處理的是除了增刪改查之外的所有其他操作,而這些操作往往差異很大,例如數(shù)據(jù)定義操作(DDL),事務(wù)的處理以及游標(biāo)用戶角色定義這些,因此在ProcessUtility模塊中,為每種操作單獨(dú)地設(shè)計(jì)了子過(guò)程(函數(shù))去處理。主要代碼在src/backend/commands下。

而剩下的我說(shuō)的特定功能子模塊,是指一些功能相對(duì)獨(dú)立和單一并且在整個(gè)查詢過(guò)程中會(huì)反復(fù)被調(diào)用的函數(shù)(我更愿意稱他們?yōu)楣ぞ吆瘮?shù)),例如各種輔助的子系統(tǒng),表達(dá)式的計(jì)算,投影運(yùn)算以及元組操作這些。


1.查詢優(yōu)化策略

在進(jìn)入這一模塊之前,我已經(jīng)簡(jiǎn)要說(shuō)明了Executor模塊和ProcessUtility模塊這兩個(gè)主要的執(zhí)行分支。這里要提到兩個(gè)概念:

可優(yōu)化語(yǔ)句和非可優(yōu)化語(yǔ)句

可優(yōu)化語(yǔ)句說(shuō)白了就是DML語(yǔ)句,這些語(yǔ)句的特點(diǎn)就是都要查詢到滿足條件的元組。這類查詢都在查詢規(guī)劃階段生成了規(guī)劃樹,而規(guī)劃樹的生成過(guò)程中會(huì)根據(jù)查詢優(yōu)化理論進(jìn)行重寫和優(yōu)化以提高查詢速度,因此稱作可優(yōu)化語(yǔ)句。

那反過(guò)來(lái)講,那些沒(méi)有生成執(zhí)行計(jì)劃樹的功能性操作就是非可優(yōu)化語(yǔ)句了。這里只是提一下這個(gè)概念,在后面的介紹中會(huì)用。

1.1五種執(zhí)行策略

上面提到,一條簡(jiǎn)單的SQL語(yǔ)句會(huì)被查詢編譯器轉(zhuǎn)化為一個(gè)執(zhí)行計(jì)劃樹或者一個(gè)非計(jì)劃樹操作。而一條復(fù)雜的SQL語(yǔ)句往往同時(shí)帶有DDL和DML語(yǔ)句,即它會(huì)被轉(zhuǎn)換為一個(gè)可執(zhí)行計(jì)劃樹和非執(zhí)行計(jì)劃樹操作的序列。而可執(zhí)行計(jì)劃樹和非可執(zhí)行計(jì)劃樹是由不同的子模塊去處理的。這樣就有了三種不同的情況,需要三種不同的策略去應(yīng)對(duì)。

然而除此之外,我們還有一種額外的情況需要考慮到:有些SQL語(yǔ)句雖然可以被轉(zhuǎn)換為一個(gè)原子操作,但是其執(zhí)行過(guò)程中由于各種原因需要能夠緩存語(yǔ)句執(zhí)行的結(jié)果,等到整個(gè)語(yǔ)句執(zhí)行完畢在返回執(zhí)行結(jié)果。

具體的說(shuō):

  • 1 對(duì)于可優(yōu)化語(yǔ)句,當(dāng)執(zhí)行修改元組操作時(shí),希望能夠返回被修改的元組(例如帶RETURNING子句的DELETE),由于原子操作的處理過(guò)程不能被可能有問(wèn)題的輸出過(guò)程終止,因此不能邊執(zhí)行邊輸出,因此需要一個(gè)緩存結(jié)構(gòu)來(lái)臨時(shí)存放執(zhí)行結(jié)果;

  • 2 某些非優(yōu)化語(yǔ)句是需要返回結(jié)果的(例如SHOW,EXPLAIN) ,因此也需要一個(gè)緩存結(jié)構(gòu)暫存處理結(jié)果。

此外,對(duì)于帶有INSERT/UPDATE/DELETE的WITH子句,會(huì)在CTE中修改數(shù)據(jù),和一般的CTE不一樣。我們也需要進(jìn)行特事特辦,特殊處理,這是第五種情況。

因此,綜合上面所說(shuō)的,我們需要有五種處理策略來(lái)解決,分別如下:

  • 1)PORTAL_ONE_SELECT:處理單個(gè)的SELECT語(yǔ)句,調(diào)用Executor模塊;

  • 2)PORTAL_ONE_RETURNING:處理帶RETURNING的UPDATE/DELETE/INSERT語(yǔ)句,調(diào)用Executor模塊;

  • 3)PORTAL_UTIL_SELECT:處理單個(gè)的數(shù)據(jù)定義語(yǔ)句,調(diào)用ProcessUtility模塊;

  • 4)PORTAL_ONE_MOD_WITH:處理帶有INSERT/UPDATE/DELETE的WITH子句的SELECT,其處理邏輯類似PORTAL_ONE_RETURNING。調(diào)用Executor模塊;

  • 5)PORTAL_MULTI_QUERY:是前面幾種策略的混合,可以處理多個(gè)原子操作。

1.2 策略的實(shí)現(xiàn)

執(zhí)行策略選擇器的工作是根據(jù)查詢編譯階段生成的計(jì)劃樹鏈表來(lái)為當(dāng)前的查詢選擇五種執(zhí)行策略中的一種。在這個(gè)過(guò)程中,執(zhí)行策略選擇器會(huì)使用數(shù)據(jù)結(jié)構(gòu)PortalData來(lái)存儲(chǔ)查詢計(jì)劃樹鏈表以及最后選中的執(zhí)行策略等信息。

對(duì)于Portal這一數(shù)據(jù)結(jié)構(gòu)(定義在src/include/utils/portal.h里),我把重要的字段信息也貼在下面吧:

typedef struct PortalData
{    /* Bookkeeping data */
    const char *name;           /* portal's name */
    ......    /* The query or queries the portal will execute */
    const char *sourceText;     /* text of query (as of 8.4, never NULL) */
    const char *commandTag;     /* command tag for original query */
    List       *stmts;          /* PlannedStmts and/or utility statements */
    ......
    ParamListInfo portalParams; /* params to pass to query */

    /* Features/options */
    PortalStrategy strategy;    /* see above */

    /* If not NULL, Executor is active; call ExecutorEnd eventually: */
    QueryDesc  *queryDesc;      /* info needed for executor invocation */

    /* If portal returns tuples, this is their tupdesc: */
    TupleDesc   tupDesc;        /* descriptor for result tuples */
    ......
}   PortalData;

對(duì)于查詢執(zhí)行器來(lái)說(shuō),在執(zhí)行一個(gè)SQL語(yǔ)句時(shí)都會(huì)以一個(gè)Portal作為輸入數(shù)據(jù),在Portal中存放了與執(zhí)行該SQL相關(guān)的所有信息,例如查詢樹、計(jì)劃樹和執(zhí)行狀態(tài)等。
Portal結(jié)構(gòu)和與之相關(guān)的主要字段的結(jié)構(gòu)如下所示:

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

這里僅僅給出了兩種可能的原子操作PlannedStmt和Query,這兩者都能包含查詢計(jì)劃樹,用于保存含有查詢的操作。當(dāng)然有些含有查詢計(jì)劃樹的原子操作不一定是SELECT語(yǔ)句,例如游標(biāo)的聲明(utilityStmt字段不為空),SELECT INTO語(yǔ)句(intoClause字段不為空)等等。

那么我們很容易想到,postgres是不是就是根據(jù)原子操作的命令類型和原子操作的個(gè)數(shù)來(lái)確定合適的執(zhí)行策略當(dāng)然呢?

YES!但是不完全

命令的類型就如下幾種:

//  src/include/nodes/nodes.htypedef enum CmdType
{
    CMD_UNKNOWN,
    CMD_SELECT,                 /* select stmt */
    CMD_UPDATE,                 /* update stmt */
    CMD_INSERT,                 /* insert stmt */
    CMD_DELETE,
    CMD_UTILITY,                /* cmds like create, destroy, copy, vacuum,
                                 * etc. */
    CMD_NOTHING                 /* dummy command for instead nothing rules
                                 * with qual */} CmdType;

那么策略到底如何呢?無(wú)非就是根據(jù)命令類型,原子操作個(gè)數(shù)以及查詢樹、計(jì)劃樹上的某些字段(比如hasModifyingCTE、utilityStmt等等)這些做判斷了,具體的我就不寫了,太占版面了,大家也不愿意看的。

執(zhí)行這一任務(wù)的函數(shù)是ChoosePortalStrategy,在src/backend/tcop/pquery.c文件中。函數(shù)邏輯比較清晰,大家有興趣可以瞅瞅。

1.3 Portal的執(zhí)行過(guò)程

好了,終于到了這里,我們講一講一個(gè)Portal是如何執(zhí)行的吧。

所有的SQL語(yǔ)句的執(zhí)行都必須從一個(gè)Portal開始,所有的Portal流程都必須要進(jìn)過(guò)下面這個(gè)流程:
大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

該流程都在exec_simple_query函數(shù)內(nèi)部進(jìn)行。過(guò)程大致如下:

  • 1)調(diào)用函數(shù)CreatePortal創(chuàng)建一個(gè)“clean”的Portal,它的內(nèi)存上下文,資源跟蹤器清理函數(shù)都已經(jīng)設(shè)置好,但是sourceText,stmts字段還未設(shè)置;

  • 2)調(diào)用函數(shù)PortalDefineQuery函數(shù)為剛剛創(chuàng)建的Portal設(shè)置sourceText,stmt等,并且設(shè)置Portal的狀態(tài)為PORTAL_DEFINED;

  • 3)調(diào)用函數(shù)PortalStart對(duì)定義好的Portal進(jìn)行初始化:

    a.調(diào)用函數(shù)ChoosePortalStrategy為portal選擇策略;
    b.如果選擇的是PORTAL_ONE_SELECT,則調(diào)用CreateQueryDesc為Portal創(chuàng)建查詢描述符;c.如果選擇的是PORTAL_ONE_RETURNING或者PORTAL_ONE_MOD_WITH,則調(diào)用ExecCleanTypeFromTL為portal創(chuàng)建返回元組的描述符;
    d.對(duì)于PORTAL_UTIL_SELECT則調(diào)用UtilityTupleDescriptor為Portal創(chuàng)建查詢描述符;
    e.對(duì)于PORTAL_MULTI_QUERY這里則不做過(guò)多操作;
    f.將Portal的狀態(tài)設(shè)置為PORTAL_READY。
  • 4)調(diào)用函數(shù)PortalRun執(zhí)行portal,這就按照既定的策略調(diào)用相關(guān)執(zhí)行部件執(zhí)行Portal;

  • 5)調(diào)用函數(shù)PortalDrop清理Portal,釋放資源。

最后畫一張圖來(lái)解釋整個(gè)流程吧:
大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

下一篇,我們細(xì)細(xì)講講可優(yōu)化語(yǔ)句和非可優(yōu)化語(yǔ)句的執(zhí)行~

作者:非我在

出處:http://www.cnblogs.com/flying-tiger/

本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利.

感謝您的閱讀。如果覺得有用的就請(qǐng)各位大神高抬貴手“推薦一下”吧!你的精神支持是博主強(qiáng)大的寫作動(dòng)力。

如果覺得我的博客有意思,歡迎點(diǎn)擊首頁(yè)左上角的“+加關(guān)注”按鈕關(guān)注我!

http://www.cnblogs.com/flying-tiger/p/6100794.html