當前位置:
首頁 > 知識 > 跟我一起讀postgresql源碼(六)——Executor

跟我一起讀postgresql源碼(六)——Executor

時光荏苒,歲月如梭。樓主已經很久沒有更新了。之前說好的一周一更的沒有做到。實在是事出有因,沒能靜下心來好好看代碼。當然這不能作為我不更新的理由,時間擠擠還是有的,拖了這麼久,該再寫點東西了,不然人就怠懶了。不過這回,我準備寫的精簡些,一方面我想偷點懶省點時間,二來畢竟寫太長大家也不一定愛看。

之前我說過的查詢分析,查詢重寫和查詢規劃都是相當於是對查詢的"編譯"。那麼編譯完了就應該按照既定的策略去執行它。本篇就來介紹查詢執行模塊的代碼(Executor),歡迎拍磚。

這部分我主要從以下五個部分介紹查詢執行模塊(很可能要分成四到五篇文章來闡述,畢竟是比查詢規劃還要複雜的模塊):

1.查詢優化策略
2.非可優化語句的執行
3.可優化語句的執行
4.計劃節點
5.其它子功能介紹

查詢執行器的框架結構如下圖所示。

跟我一起讀postgresql源碼(六)——Executor

在exec_simple_query函數中,調用查詢編譯模塊之後,就進入了查詢執行器模塊。在該模塊中,就是按照前一階段查詢規劃模塊鎖生成的查詢計劃,有機第調用存儲、索引,並發等模塊來完成數據的讀取或者修改的過程。

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

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

這兩個子模塊的處理邏輯相差很大,執行過程和先關數據結構差異也很大。

對於Executor模塊,它根據輸入的查詢計劃樹按部就班地處理數據表中元組的增刪改查(DML)操作,它的執行邏輯是統一的(所有的增刪改查最後都歸結為SELECT,只是分別在SELECT的基礎上進行一些額外的操作)。其主要代碼放在src/backend/executor下。

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

而剩下的我說的特定功能子模塊,是指一些功能相對獨立和單一併且在整個查詢過程中會反覆被調用的函數(我更願意稱他們為工具函數),例如各種輔助的子系統,表達式的計算,投影運算以及元組操作這些。


1.查詢優化策略

在進入這一模塊之前,我已經簡要說明了Executor模塊和ProcessUtility模塊這兩個主要的執行分支。這裡要提到兩個概念:


可優化語句和非可優化語句

可優化語句說白了就是DML語句,這些語句的特點就是都要查詢到滿足條件的元組。這類查詢都在查詢規劃階段生成了規劃樹,而規劃樹的生成過程中會根據查詢優化理論進行重寫和優化以提高查詢速度,因此稱作可優化語句。

那反過來講,那些沒有生成執行計劃樹的功能性操作就是非可優化語句了。這裡只是提一下這個概念,在後面的介紹中會用。

1.1五種執行策略

上面提到,一條簡單的SQL語句會被查詢編譯器轉化為一個執行計劃樹或者一個非計劃樹操作。而一條複雜的SQL語句往往同時帶有DDL和DML語句,即它會被轉換為一個可執行計劃樹和非執行計劃樹操作的序列。而可執行計劃樹和非可執行計劃樹是由不同的子模塊去處理的。這樣就有了三種不同的情況,需要三種不同的策略去應對。

然而除此之外,我們還有一種額外的情況需要考慮到:有些SQL語句雖然可以被轉換為一個原子操作,但是其執行過程中由於各種原因需要能夠緩存語句執行的結果,等到整個語句執行完畢在返回執行結果。

具體的說:

  • 1 對於可優化語句,當執行修改元組操作時,希望能夠返回被修改的元組(例如帶RETURNING子句的DELETE),由於原子操作的處理過程不能被可能有問題的輸出過程終止,因此不能邊執行邊輸出,因此需要一個緩存結構來臨時存放執行結果;

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

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

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

  • 1)PORTAL_ONE_SELECT:處理單個的SELECT語句,調用Executor模塊;

  • 2)PORTAL_ONE_RETURNING:處理帶RETURNING的UPDATE/DELETE/INSERT語句,調用Executor模塊;

  • 3)PORTAL_UTIL_SELECT:處理單個的數據定義語句,調用ProcessUtility模塊;

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

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

1.2 策略的實現

執行策略選擇器的工作是根據查詢編譯階段生成的計劃樹鏈表來為當前的查詢選擇五種執行策略中的一種。在這個過程中,執行策略選擇器會使用數據結構PortalData來存儲查詢計劃樹鏈表以及最後選中的執行策略等信息。

對於Portal這一數據結構(定義在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;

對於查詢執行器來說,在執行一個SQL語句時都會以一個Portal作為輸入數據,在Portal中存放了與執行該SQL相關的所有信息,例如查詢樹、計劃樹和執行狀態等。 Portal結構和與之相關的主要欄位的結構如下所示:

跟我一起讀postgresql源碼(六)——Executor

這裡僅僅給出了兩種可能的原子操作PlannedStmt和Query,這兩者都能包含查詢計劃樹,用於保存含有查詢的操作。當然有些含有查詢計劃樹的原子操作不一定是SELECT語句,例如游標的聲明(utilityStmt欄位不為空),SELECT INTO語句(intoClause欄位不為空)等等。

那麼我們很容易想到,postgres是不是就是根據原子操作的命令類型和原子操作的個數來確定合適的執行策略當然呢?

YES!但是不完全

命令的類型就如下幾種:

// src/include/nodes/nodes.h
typedef 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;

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

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

1.3 Portal的執行過程

好了,終於到了這裡,我們講一講一個Portal是如何執行的吧。

所有的SQL語句的執行都必須從一個Portal開始,所有的Portal流程都必須要進過下面這個流程:

跟我一起讀postgresql源碼(六)——Executor

該流程都在exec_simple_query函數內部進行。過程大致如下:

  • 1)調用函數CreatePortal創建一個「clean」的Portal,它的內存上下文,資源跟蹤器清理函數都已經設置好,但是sourceText,stmts欄位還未設置;
  • 2)調用函數PortalDefineQuery函數為剛剛創建的Portal設置sourceText,stmt等,並且設置Portal的狀態為PORTAL_DEFINED;
  • 3)調用函數PortalStart對定義好的Portal進行初始化:

    a.調用函數ChoosePortalStrategy為portal選擇策略; b.如果選擇的是PORTAL_ONE_SELECT,則調用CreateQueryDesc為Portal創建查詢描述符; c.如果選擇的是PORTAL_ONE_RETURNING或者PORTAL_ONE_MOD_WITH,則調用ExecCleanTypeFromTL為portal創建返回元組的描述符; d.對於PORTAL_UTIL_SELECT則調用UtilityTupleDescriptor為Portal創建查詢描述符; e.對於PORTAL_MULTI_QUERY這裡則不做過多操作; f.將Portal的狀態設置為PORTAL_READY。

  • 4)調用函數PortalRun執行portal,這就按照既定的策略調用相關執行部件執行Portal;
  • 5)調用函數PortalDrop清理Portal,釋放資源。

最後畫一張圖來解釋整個流程吧:

跟我一起讀postgresql源碼(六)——Executor

下一篇,我們細細講講可優化語句和非可優化語句的執行~

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 達人科技 的精彩文章:

mysql主從複製原理探索
ASP.NET Core MVC 控制器創建與依賴注入
自定義事件解決重複請求BUG
官方Tomcat鏡像Dockerfile分析及鏡像使用
基於多線程方式的串列通信介面數據接收案例

TAG:達人科技 |

您可能感興趣

學習postgresql第一步——安裝postgresql
postgresql的copy語句和備份恢復
怎樣在 Kubernetes 上運行 PostgreSQL
雲資料庫TencentDBforPostgreSQL
Google 的雲端資料庫Cloud SQL:開始支持 PostgreSQL
乾貨分享!CynosDB for PostgreSQL 架構淺析
力壓 MongoDB、Redis,PostgreSQL 蟬聯「年度資料庫」!
微軟收購Citus Data擴展PostgreSQL服務
開源資料庫 PostgreSQL、MariaDB 和 SQLite 的對比
PostgreSQL 12.0 Beta 發布
GIS on CentOS 7之PostgreSQL&PostGIS
PostgreSQL簡單使用
微軟雲計算PostgreSQL資料庫支持GraphQL
為什麼PostgreSQL越來越火?
Azure虛擬網路整合MySQL、PostgreSQL服務
外文翻譯丨「王者對戰」之 MySQL 8 vs PostgreSQL 10(深度)
GitLab:因「大腦分裂問題」 5台PostgreSQL 3 台徹底趴下,性能迅速下降
3 月全球資料庫排名:PostgreSQL 再迎暴漲
3月全球資料庫排名:PostgreSQL再迎暴漲
三款免費的PostgreSQL監控工具,DBA收藏了