數據庫集簇的邏輯結構
數據庫集簇(database cluster)是指由單個PostgreSQL服務器實例管理的數據庫集合。(解讀:數據庫集簇是集合,其元素是數據庫。一個PostgreSQL服務器實例只會在單機上運行並管理單個數據庫集簇。注意這里表述中的兩個”單“,即單機單個集簇,說明服務器實例不能跨多個主機,不能管理多個數據庫集簇)。數據庫集簇在本質上就是一個文件目錄,其包含着一些列子目錄與文件。(例如執行/opt/pgsql/bin/initdb -D /pgdata/10/data -W 在指定目錄下創建基礎目錄,從而初始化一個新的數據庫集簇,這里的/pgdata/10/data就是數據庫集簇)基礎目錄文件樹見文末。
注:PostgreSQL的數據庫集簇(database cluster)概念和高可用數據庫集群不同,這里的集簇(cluster)僅表示多個邏輯的數據庫在用一個數據庫實例中。
邏輯結構
區分數據庫和數據庫實例:PostgreSQL數據庫是由一系列位於文件系統上的物理文件組成(通常這些物理文件被稱為數據庫);物理文件加上進程管理的內存和管理這些物理文件的進程稱為該數據庫的實例。在上述初始化數據目錄的操作中,initdb工具自動創建了template0、template1和postgres數據庫(解讀:postgres是默認數據庫,template0和template1是生成其他數據庫的模板)。一個數據庫集簇可以包含多個數據庫、多個User,梅特數據庫以及數據庫中的表、索引等都有它們的擁有者:User。
創建一個Database時會為這個Database創建一個名為public的默認Schema,每個Database可以多個Schema,在數據庫中創建表、索引等時如果沒有指定Schema,都會在public這個Schema中。(解讀:Schema可以理解為一個數據庫中的命名空間,在數據庫中創建的所有對象都在Schema中創建,一個用戶可以從同一個客戶端連接中訪問不同的Schema)。不同的Schema中可以有多個相同名稱的表、索引、視圖等。
數據庫對象
數據庫是數據庫對象(database object)的集合(例如,索引、視圖、函數等都是數據庫對象)。在PostgreSQL中,數據庫本身也是數據庫對象,並在邏輯上彼此分離。除數據庫之外的其他數據庫對象(如表、索引等)都歸屬於各自相應的數據庫。雖然隸屬於同一個數據庫集簇,但無法直接從集簇中的一個數據庫訪問到。
表空間也是數據庫對象,在PostgreSQL中最大的邏輯存儲單元就是表空間。數據庫中創建的對象,包括數據庫本身都保存在表空間中(例如表、索引和整個數據庫)。在創建數據庫對象時,可以指定數據庫對象的表空間,如果不指定則使用默認表空間。初始化數據庫目錄時會自動創建pg_default和pg_global兩個表空間。
- pg_global表空間的物理文件位置在數據目錄的global目錄中,它用來保存系統表。
- pg_default表空間的物理文件位置在數據目錄中的base目錄,是template0和template1數據庫的默認表空間。創建數據庫時,默認從template1數據庫進行克隆。因此除非特別指定新建數據庫的表空間,默認使用template1的表空間。
在PostgreSQL內部,所有數據庫對象都通過相應的對象標識符(object identifier, oid)進行管理,這些標識符是無符號的4字節整型。數據庫對象和各個oid之間的關系存儲在適當的系統目錄中,具體取決於對象的類型。
- 數據庫的oid存儲在pg_database系統表中。
- 數據庫中的表、索引、序列等對象的oid存儲在pg_class系統表中。
從base文件目錄下可以看到template0、template1和postgres對應的oid文件,也即相應的數據庫文件。
pg_class系統表的oid為1259,它位於base目錄postgres目錄下,處於pg_default表空間中。
pg_database系統表的oid為1262,它位於global目錄下,處於pg_global表空間中。
pg_default表空間的oid為1663,但是沒有找到以1663命名的文件夾。
pg_global表空間的oid為1664,但是沒有找到以1664命名的文件夾。
OID通常是從1開始分配,但在初始化數據集簇時,會先將一部分OID分配給系統表、系統表元祖、系統表上的索引等數據庫對象,這一部分OID可以在系統表所對應的頭文件中找到。同時,為了給后續版本留下擴展的余地,初始化數據集簇時還會預留一部分OID資源。這樣,在系統運行時可分配的OID資源實際是從16384開始的。在PostgreSQL源代碼src/include/catalog子目錄下有一個shell腳本unused_oids用來輸出當前版本中預分配和預留的OID的使用情況。
對於用戶表的元祖,是可以在創建用戶表時選擇是否具有OID屬性的。如果在CREATE TABLE語句中使用了WITH OIDS選項,則該用戶表中插入的每一個元祖都將被分配一個OID。否則,默認狀態下用戶表的元祖是沒有OID屬性的。
OID的分配由系統中的一個全局OID計數器來實現,每次需要分配新的OID時,就從該計數器中取當前的OID,然后該計數器將會加1。OID分配時會采用互斥鎖加以鎖定以避免多個要求分配OID的請求獲得同一個OID。
數據庫的布局
如下圖所示,一個數據庫與base子目錄下的一個子目錄對應,且該子目錄的名稱與相應數據庫的oid相同。例如,當數據庫sampledb的oid為16384時,它對應的子目錄名稱就是16384。數據庫中的每個表和索引都至少在相應子目錄下存儲為一個文件。
表空間的布局
PostgreSQL中的表空間對應一個包含基礎目錄之外數據的目錄(附加數據區域)。執行CREATE TABLESPACE語句會在指定目錄下創建表空間。在該目錄下還會創建版本特定的子目錄(PG_主版本號_目錄版本號)。每個用戶定義的表空間在PGDATA/pg_tblspc目錄里面都有一個符號鏈接,它指向表空間的物理目錄,該符號鏈接用表空間的OID命名。系統默認創建的表空間pg_default和pg_global並沒有通過符號鏈接的方式指向其物理目錄,而是直接對應PGDATA/base和PGDATA/global。
如果在mytblspc下創建新表,但新表所屬的數據庫卻創建在基礎目錄下,那么PG會首先在版本特定的子目錄下創建名稱與現有數據庫oid相同的新目錄,然后將新表文件放置在剛創建的目錄下。如下圖Database Directory所指示的一樣。
數據目錄結構
/pgdata/10/data
|--- PG_VERSION PostgreSQL主版本號文件
|--- postgresql.conf 全局配置文件,除認證外其他行為由其配置
|--- postgresql.auto.conf 只保存ALTER SYSTEM命令修改的參數
|--- pg_hba.conf 全局配置文件,負責客戶端的連接和認證配置
|--- pg_ident.conf 控制PostgreSQL用戶名映射
|--- base
|--- 1 template1數據庫目錄
|--- 13213 template0數據庫目錄
|--- 13214 postgres數據庫目錄
|--- pg_serial 包含已提交的可序列化事務信息的子目錄,初始化后為空目錄
|--- pg_tblspc 包含指向表空間的符號鏈接的子目錄,初始化后為空目錄
|--- global 包含集簇范圍的表的子目錄,比如pg_database
|--- pg_logical 包含用於邏輯復制的狀態數據的子目錄
|--- pg_snapshots 包含導出的快照的子目錄
|--- pg_twophase 用於預備事務狀態文件的子目錄
|--- pg_commit_ts 事務提交的時間戳數據
|--- pg_multixact 多事務狀態數據
|--- pg_stat 統計子系統的永久文件
|--- pg_dynshmem 動態共享內存子系統中使用的文件
|--- pg_notify LISTEN/NOTIFY狀態數據
|--- pg_stat_tmp 統計子系統的臨時文件
|--- pg_wal WAL段文件,從pg_xlog重命名而來
|--- pg_replslot 復制槽數據
|--- pg_subtrans 子事務狀態數據
|--- pg_xact 事務提交狀態數據,從pg_clog重命名而來
數據庫集簇的物理結構
每個表和索引都存儲在其所屬數據庫目錄下的獨立文件里,以該表或者該索引的filenode號命名,該號碼記錄在該表或索引在系統表pg_class中對應元祖的relfilenode屬性中。在表或索引超過1GB之后,它就被分裂成多個1GB大小的段。第一個段的文件名和filenode相同,隨后的段命名為filemode.1,filenode.2 ......這樣的策略避免了在某些有文件大小限制的平台上可能出現的問題。如果一個表的有些屬性要存儲相當大的數據,那么就會有個與之相關聯的TOAST表,用於存儲無法在數據行中放置的超大外置數據。表對應的pg_class元祖的reltoastrelid屬性記錄了她的TOAST表OID。
表和元組的組織方式:在PG中,同一個表中的元組按照創建順序依次插入到表文件中,元組之間不進行關聯,這樣的表文件稱為堆文件。PG系統中包含了四種堆文件:普通堆(ordinary cataloged heap)、臨時堆(temporary heap)、序列(SEQUENCE relation,一種特殊的單行表)和TOAST表(TOAST table)。臨時堆的結構與普通堆相同,但臨時堆僅在會話過程中臨時創建,會話結束會自動刪除。序列則是一種元組值自動增加的特殊堆。TOAST表其實也是一種普通堆,但是它被專門用於存儲變長數據。盡管這幾種堆文件功能各異,但在底層的文件結構卻是相似的。每個堆文件都是由多個文件塊組成。數據的讀寫是以塊為單位,塊默認大小為8K,在編譯PG時指定的BLCKSZ決定塊的大小。
文件塊在物理磁盤上的存儲形式如下。Page Header Data是長度為20字節的頁頭數據,包含該文件塊的一般信息,如空閑空間的起始和結束位置、Special space的開始位置、項指針的開始位置、標志信息,如是否存在空閑項指針、是否所有的元組都可見。
Linp是ItemIdData類型的數組,ItemIdData類型由lp_off、lp_flags和lp_len三個屬性組成。每個ItemIdDate結構用來指向文件塊中的一個元組,其中lp_off是元組在文件塊中的偏移量,而lp_len則說明了該元組的長度,lp_flags表示元組的狀態(分為未使用、正常使用、HOT重定向和死亡四種狀態)。每個Linp數組元素的長度為4字節。
Freespace是指未分配的空間(空閑空間),新插入頁面中的元組及其對應的Linp元素都將從這部分空間中來分配,其中Linp元素從Freespace的開頭開始分配,而新元組數據則從尾部開始分配。
Special space是特殊空間。用於存放與索引方法相關的特定數據,不同的索引方法在Special space中存放不同的數據。由於索引文件的文件塊結構和普通表文件的相同,因此Special space在普通表文件塊中並沒有使用,其內容被置為空。
參考:
PostgreSQL實戰
博客中的流程圖均使用ProcessOn繪制(一款在線流程圖繪制軟件),網址:https://www.processon.com/
轉載請注明出處