1.數據定義語句的執行
數據定義語句(也就是之前我提到的非可優化語句)是一類用於定義數據模式、函數等的功能性語句。不同於元組增刪査改的操作,其處理方式是為每一種類型的描述語句調用相應的處理函數。
數據定義語句的執行流程最終會進入到ProcessUtility處理器,然后執行語句對應的不同處理過程。由於數據定義語句的種類很多,因此整個處理過程中的數據結構和方式種類繁冗、復雜,但流程相對簡單、固定。這里我們以Create table為例說明數據定義語句的具體處理過程。
1.1數據定義語句執行流程
由於ProcessUtility需要處理所有類型的數據定義語句,因此ProcessUtility通過判斷數據結構中NodeTag字段的值來區分各種不同節點,並引導執行流程進入相應的處理函數。
相同類別的語句處理過程涉及內容相近,實現思想和主要過程相似。例如,事務類處理主要是對於當前事務的狀態的判斷和轉換;游標類處理的主要思想是首次將執行一個査詢計划樹(Plantree),將結果緩存在Portal指向的特殊結構中,然后按照要求獲取元組數據;表、屬性管理類主要涉及權限管理、修改相應系統表以及關系表的存儲類別操作等。由於數據定義語句的種類多達上百種,我們下面將以一個創建表的例子來介紹數據定義語句的執行流程。
針對各種不同的査詢樹,査詢編譯器在執行處理前會做一些額外的處理對査詢樹進行分析、處理與轉換。創建表的語句會用函數transformCreateStmt進行査詢樹的處理。這些處理過程可能會在當前操作之前和之后增加一些新的操作(例如在創建表的操作之前增加創建serial序列表操作、之后增加創建觸發器用於外鍵約束操作等),也可能會執行對數據結構的處理操作(例如將CreateStmt節點tableElts字段中CONST_CHECK類型的Constraint節點轉存到CreateStmt的ccmstraints鏈表中等)。由於這些處理過程會產生一些新的操作,因此最終會生成一個由多個操作構成的鏈表。因此,執行過程需要依次掃描該鏈表,為每一個原子操作調用相應的處理函數。
1.2執行實例
例 創建一個名為course的數據表,此表有三個屬性:編號(no,自增屬性)、姓名
(name)非空、學分(credit)非負。其中,包含了一個約束定義,主鍵被定義為編號(no)。對應
的SQL語句如T:
CREATE TABLE course (
no SERIAL,
name VARCHAR,
credit INT,
CONSTRAINT con1 CHECK(credit > = 0 AND name <> ''),
PRIMARY KEY(no)
);
系統首先會對査詢語句進行詞法和語法分析,將査詢語句構造為査詢樹的鏈表。然后,針對鏈
表中的每一個査詢樹進行如下的處理過程(下例僅有一個T_CreateStmt類型的査詢樹):
-
1)分析和重寫查詢樹。
-
2)生成査詢計划。
-
3)創建及初始化Portal。
-
4)調用Portal執行過程。
-
5)調用Portal清理過程。
下面給出了上述査詢語句執行時的主要函數調用流程。
pg_parse_query
|
v
pg_analyze_and_rewrite
|
v
PortalStart -> ChoosePortalStrategy
|
v
PortalRun -> PortalRunMulti -> PortalRunUtility -> ProcessUtility -> standard_ProcessUtility -> ProcessUtilitySlow
|
v
PortalDrop
在上面的例子中,査詢編譯器會生成一個僅包含一個T_CreateStmt類型節點的査詢樹鏈表,因此對應的Portal的stmts字段中也只包含一個T_CreateStmt類型節點。ChoosePortalStrategy函數根據stmts字段值選擇策略時會選擇PORTAL_MULTI_QUERY策略。在接下來的PortalRun函數中將會調用PortalRunMuti來執行PORTAL_MULTI_QUERY策略,將會把處理流程引導到ProcessUtility中。ProcessUtility將首先調用函數transformCreateStmt對T_CreateStmt節點進行轉換處理,流程如下所示。該過程會做如下轉換:
ProcessUtilitySlow:
case T_CreateStmt:
case T_CreateForeignTableStmt:
transformCreateStmt()
|
v
foreach(l, stmts){
if (IsA(stmt, CreateStmt)) or IsA(stmt, CreateForeignTableStmt)
...
DefineRelation
...
else
ProcessUtility
......
}
-
將主鍵約束改為創建唯一索引(T_IndexStmt節點)。
-
將自增類型轉換為int4oid,並附加創建專用的SERIAL表(用於記錄自增字段,將形成一個 T_CreateSeqStnU 節點)操作
-
增加CONSTR_DEFAULT類型約束作為默認值(被定義為調用函數nextval)。
創建SERIAL表(T_CreateSeqStmt節點)的操作會被放在stmts鏈表中T_CreateStmt節點之前的位置,創建唯一約束索引(T_IndexStmt節點)的操作被放置在T_CreateStmt節點
之后。最后還會將單獨定義或與屬性同時定義的CONSTR_CHECK類型約束全部轉移到T_CreateStmt節點的constraints字段所指向的鏈表中。
最后,transformCreateStmt將原有的 T_CreateStmt操作轉換為一個操作序列:依次為T_CreateSeqStmt (創建序列表)、T_CreateStmt (創建數據表)、T_IndexStmt (創建唯一約束索引)。
CREATE SERIAL TABLE course_no_seq;--用於產生自增序列
CREATE TABLE course (
noint40id DEFAULT nextval (),
nameVARCHAR,
creditINT,
CONSTRAINT coni CHECK (credit >=0 AND name <> "),
CREATE INDEX course_pkey;--用於唯一檢査
之后ProcessUtility將逐個對序列中的操作進行處理。對T_CreateStmt操作將會調用DefineRelation進行數據表的創建,而其他節點則會通過遞歸調用ProcessUtility進人相應的處理過程。下面展示了 T_CreateStmt操作的處理過程
創建表的過程由函數DefineRelation完成,其流程如下:
-
- 進行權限檢査,確定當前用戶是否有權限創建表。
-
- 對表創建語句中的WITH子句進行解析(transfbrmRelOptions)。
-
- 調用heap_reloptions對參數進行合法性驗證。
-
- 使用MergeAttributes,將繼承的屬性合並到表屬性定義中。
-
- 調用BuildDescForRelation利用合並后的厲性定義鏈表創建tupleDesc結構(這個結構用於描述元組各屬性結構等信息)。
-
- 決定是否使用系統屬性OID (interpretOidsOption)。
-
- 對屬性定義鏈表中的每一個屬性進行處理,査看是否有默認值、表達式或約束檢査。
-
- 使用heap_create_with_catalog創建表的物理文件並在相應的系統表中注冊。
-
9)用StoreCataloglnheritance存儲表的繼承關系。
-
10)處理表中新增的約束與默認值(AddRleationNewConstraints)。
DefineRelation
->
Permission check
transformRelOptions()
heap_reloptions()
MergeAttributes()
BuildDescForRelation()
interpretOidsOption()
CONSTR_DEFAULT or CONSTR_CHECK
heap_create_with_catalog()
StoreCatalogInheritance()
AddRelationNewConstraints()
ObjectAddressSet()
表創建函數的主要功能是由heap_create_with_catalog完成的,之前的各種操作主要是構造heap_create_with_catalog所需要的參數。例如,WITH子句處理主要完成其中存儲相關參數的處理,以便存人pg_class系統表的reloptions字段中;BuildDescForRelation主要處理表定義中屬性名、類型、非空約束以便構造pg_attribute系統表相關內容。
heap_create_with_catalog函數首先會根據要創建表的屬性描述信息、表的名稱、命名空間等使用heap_create創建一個RelationData結構並放人RelCache,並根據這些信息通過調用RelalionCreateStorage函數創建物理文件。然后調用AddNewRelationType,向pg_type中增加一條關於該表的記錄。AddNewRelationTuple則會將表的相關信息插人pg_class系統表中,而AddNewAttributeTuples將表的每個屬性記錄到pg_attribute系統表中。最后還需要通過調用StoreConstraints將約束和默認值分別存儲到 pg_constraint 和 pg_attrdef 中。
1.3主要的功能處理函數
從創建表的例子可以看到,功能處理器(ProcessUtility)本身只作為入口選擇函數,它會根據輸人的節點類型調用相應的處理過程。除了創建表的處理過程之外,下面列出了幾種常見的輸人節點類型,並給出了其對應處理函數以及其功能簡介。
注:本文參考了《Postgresql數據庫內核分析》一書。