數據庫內核分析之GPDB and PostgreSQL Portal
GPDB and PostgreSQL Portal內核分析
0.導論
Portal(門戶),也稱為策略選擇模塊,根據sql語句類型選擇不同的執行模塊(ProcessUtility、Executor)。
SQL語句類型包括:可優化語句、數據定義語句。
可優化語句包括DML,像insert/update/select等語句,這類語句特點是查詢滿足條件的元組返回給用戶或者元組操作后寫入磁盤,之所以稱之為可優化語句是因為這類語句通常會被優化器進行重寫與優化,從而加快查詢速度。
數據定義語句主要是功能性語句,例如:DDL(Create、Alter、Drop)、DCL(Grant、COMMIT、ROLLBACK)等。
1.Portal
1.1 入口層
QD執行會從exec_simple_query進入,QE執行從exec_mpp_query進入。
1.2 Portal層
1.2.1 初識Portal
首先初識Portal內部數據結構:
策略只包含一個SELECT查詢。
包含一個INSERT/UPDATE/DELETE查詢,且帶RETURNING條件。
包含一個SELECT查詢并且有修改的CTE。
例如:下面這個就不是PORTAL_ONE_MOD_WITH,而是PORTAL_MULTI_QUERY。
包含一個utility語句,且該語句執行會返回像SELECT那樣有輸出結果。
其他情況,例如:PORTAL_MULTI_QUERY + PORTAL_MULTI_QUERY
- PORTAL_MULTI_QUERY
- PORTAL_UTIL_SELECT
- PORTAL_ONE_MOD_WITH
- PORTAL_ONE_RETURNING
- PORTAL_ONE_SELECT
狀態
- PORTAL_NEW
- PORTAL_DEFINED
- PORTAL_READY
- PORTAL_QUEUE
- PORTAL_ACTIVE
- PORTAL_DONE
- PORTAL_FAILED
這幾個狀態會在下面依次引入。
1.2.2 CreatePortal
創建一個新的Portal,傳入參數均為: CreatePortal("", true, true),表示創建一個匿名的Portal,允許重復且重復后保持沉默。
CreatePortal邏輯:
- 根據傳入的第一個參數name從哈希表中查找
- 根據傳入的第二個參數allowDup,如果第一步查找到,從哈希表中決定是否刪除。如果true,則刪除,否則報錯。在哈希表中查找到Portal且允許重復的情況下,在QD節點上會根據第三個參數dupSilent決定是否輸出告警信息。
- 創建一個新的Portal,并初始化相應參數。
執行完畢后,便創建好了一個狀態為PORTAL_NEW的Portal。
1.2.3 PortalDefineQuery
定義portal數據,包含了:查詢語句sourceText、PlannedStmts、查詢完成標記qc。
注意:QD上根據傳遞進來的stmt來設置nodeTag,但是QE上為T_Query,因為QE上不是parsed statement,所以不是 T_SelectStmt。
最終設置Portal狀態為PORTAL_DEFINED。
1.2.4 PortalStart
準備好portal,主要有如下幾步:
- 設置ddesc,該信息為QD到QE上的額外信息,QD上為NULL,QE上不為NULL。
- 設置全局參數,例如:當前活躍的portal、resourceOwner、context。
- 設置portal參數字段:portalParams,同樣QD上為NULL,QE上不為NULL。
- 設置portal策略(ChoosePortalStrategy)。
如下圖所示:輸入為查詢計劃鏈表,針對PORTAL_ONE_SELECT、PORTAL_ONE_MOD_WITH、PORTAL_UTIL_SELECT、PORTAL_ONE_RETURNING都是要求一個計劃,首先判斷是Query還是PlannedStmt,一般情況下的查詢語句基本都是PlannedStmt,對于像PREPARE st(int) as select * from t1之類utility語句第一次調用ChoosePortalStrategy返回PORTAL_MULTI_QUERY(命中PlannedStmt),第二次調用返回PORTAL_ONE_SELECT(命中Query)。
choose
5. 根據portal策略初始化portal,最重要的是初始化tupDesc與cursor postion。
例如:"QUERY PLAN"、"t1_id、col1"就是tupDesc。
不同tupleDesc函數區別
- ExecTypeFromTL
skip resjunk column
- ExecCleanTypeFromTL
with resjunk column
6. 在執行Portal過程中發生異常,設置portal的狀態為PORTAL_FAILED;否則,下一步。
7. 設置Portal狀態為PORTAL_READY。
1.2.5 PortalRun
根據sql的語句類型選擇不同的執行路徑,獲取元組數據,完成portal工作,運行完之后要么Done要么下一輪(READY,而非ACTIVE)。
portal策略執行路徑如下:
- PORTAL_ONE_SELECT
set result = portal->atEnd
- PORTAL_ONE_RETURNING|PORTAL_ONE_MOD_WITH|PORTAL_UTIL_SELECT
獲取時數據方向包含前進/后退
可以從holdStore中獲取,也可以從ExectorRun中獲取
填充holdStore(見下方)
調用PortalRunSelect返回n行數據
設置狀態為PORTAL_READY
設置是否完成運行標記為portal->atEnd
- PORTAL_MULTI_QUERY
調用PortalRunMulti
設置狀態為PORTAL_Done
設置是否完成運行標記為true
此外,上述圖中填充holdStore邏輯如下:
- 調用PortalCreateHoldStore填充portal->holdStore,通過工廠函數CreateDestReceiver構造DestReceiver(子類:TStoreState);
- 根據portal策略執行查詢:PORTAL_ONE_RETURNING、PORTAL_ONE_MOD_WITH 調用PortalRunMulti,PORTAL_UTIL_SELECT調用PortalRunUtility。
PortalRunUtility
PortalRunMulti
ProcessQuery
PortalRunUtility
utilityStmt
not utilityStmt
PORTAL_ONE_RETURNING、PORTAL_ONE_MOD_WITH
PORTAL_UTIL_SELECT
2.游標Cursor
2.1 打開游標
如果不想一次執行整個命令,可以設置一個封裝該命令的游標(cursor), 然后每次讀取幾行命令結果。
例如:
該命令運行機制為:首先識別到是一個數據定義語句,便會調用ProcessUtility,隨后解析從PlannedStmt中的utilityStmt識別出是一個T_DeclareCursorStmt節點,調用PerformCursorOpen執行Declare cursor命令。
PerformCursorOpen處理邏輯如下:
- query重寫
- 優化器優化,生成PlannedStmt
- 創建Portal(名字為游標名),僅調用PortalStart
2.2 關閉游標
關閉游標,實際就是關閉Portal,調用PerformPortalClose。
如下兩個操作:
如果傳入的名字為空,則是CLOSE ALL關閉所有非活躍portal,否則,只關閉指定的portal(cursor)。
2.3 FETCH or MOVE
FETCH與MOVE語法分別如下:
FETCH從游標中檢索n行到目標中, 目標可以是一個行變量、記錄變量、逗號分隔的普通變量列表, 就像SELECT INTO一樣, 如果沒有獲取到數據,目標會設為NULL。
MOVE重新定位一個游標,而不需要檢索任何數據,例如:一旦游標位置確定,則可以刪除或更新行。
從實現層面兩者都會進入到PerformPortalFetch,都被解析為FetchStmt,內部有個成員ismove決定是MOVE還是FETCH。
不管是哪個,都會指定cursor名,有了這個名字,便知道了portal,隨后調用PortalRunFetch來獲取結果。
PortalRunFetch內部會像PortalRun運行一樣,首先設置portal狀態為Active,隨后根據策略選擇不同的調用鏈。
- PORTAL_ONE_SELECT
調用DoPortalRunFetch
- PORTAL_ONE_RETURNING|PORTAL_ONE_MOD_WITH|PORTAL_UTIL_SELECT
首先判斷portal內部是否有holdStore,如果沒有會調用FillPortalStore,隨后調用DoPortalRunFetch。
DoPortalRunFetch內部實現,會考慮傳入的direction,決定是前向還是后向等不同方向的掃描,最后調用PortalRunSelect獲取數據,注意:gpdb不支持backward scan,但是pg支持。