數(shù)據(jù)庫集群的邏輯結(jié)構(gòu)
數(shù)據(jù)庫集群是由 PostgreSQL 服務(wù)器管理 的數(shù)據(jù)庫集合。 PostgreSQL 中的術(shù)語“數(shù)據(jù)庫集群”并不意味著“一組數(shù)據(jù)庫服務(wù)器”。PostgreSQL 服務(wù)器在單個主機上運行并管理單個數(shù)據(jù)庫集群。
數(shù)據(jù)庫是數(shù)據(jù)庫對象的集合。在關(guān)系數(shù)據(jù)庫理論中,數(shù)據(jù)庫對象是一種用于存儲或引用數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。(堆)表就是它的一個典型例子,還有很多像索引、序列、視圖、函數(shù)等。在 PostgreSQL 中,數(shù)據(jù)庫本身也是數(shù)據(jù)庫對象,并且在邏輯上是相互分離的。所有其他數(shù)據(jù)庫對象(例如,表、索引等)都屬于它們各自的數(shù)據(jù)庫。

PostgreSQL 中的所有數(shù)據(jù)庫對象都由各自的對象標識符 (OID)進行內(nèi)部管理,它們是無符號的 4 字節(jié)整數(shù)。根據(jù)對象的類型,數(shù)據(jù)庫對象和相應(yīng) OID 之間的關(guān)系存儲在適當?shù)南到y(tǒng)目錄中。例如,數(shù)據(jù)庫和堆表的 OID 分別存儲在pg_database和pg_class中。
openGauss=# select datname, oid from pg_database where datname='test';
datname | oid
---------+-------
test | 16386
(1 row)
數(shù)據(jù)庫集群的物理結(jié)構(gòu)
數(shù)據(jù)庫集群基本上是 一個稱為基本目錄的目錄,它包含一些子目錄和大量文件。如果您執(zhí)行initdb實用程序來初始化新的數(shù)據(jù)庫集群,則會在指定目錄下創(chuàng)建一個基本目錄。雖然不是強制性的,但基本目錄的路徑通常設(shè)置為環(huán)境變量PGDATA。
下圖顯示了 PostgreSQL 中的數(shù)據(jù)庫集群示例。數(shù)據(jù)庫是基本子目錄下的子目錄,每個表和索引都是(至少)一個存儲在其所屬數(shù)據(jù)庫的子目錄下的文件。還有幾個包含特定數(shù)據(jù)和配置文件的子目錄。雖然 PostgreSQL 支持tablespaces,但該術(shù)語的含義與其他 RDBMS 不同。PostgreSQL 中的表空間是一個包含基本目錄之外的一些數(shù)據(jù)的目錄。

數(shù)據(jù)庫集群布局
根目錄文件
| 文件 | 描述 |
|---|---|
| PG_VERSION | 包含 PostgreSQL 主要版本號的文件 |
| pg_hba.conf | 用于控制 PosgreSQL 客戶端身份驗證的文件 |
| pg_ident.conf | 一個控制 PostgreSQL 用戶名映射的文件 |
| postgresql.conf | 用于設(shè)置配置參數(shù)的文件 |
| postgresql.auto.conf | 用于存儲在 ALTER SYSTEM(版本 9.4 或更高版本)中設(shè)置的配置參數(shù)的文件 |
| postmaster.opts | 記錄服務(wù)器上次啟動時使用的命令行選項的文件 |
子目錄
| 子目錄 | 描述 |
|---|---|
| base/ | 包含每個數(shù)據(jù)庫子目錄的子目錄。 |
| global/ | 包含集群范圍表的子目錄,例如 pg_database 和 pg_control。 |
| pg_commit_ts/ | 包含事務(wù)提交時間戳數(shù)據(jù)的子目錄。版本 9.5 或更高版本。 |
| pg_clog/(9.6 或更早版本) | 包含事務(wù)提交狀態(tài)數(shù)據(jù)的子目錄。在版本 10 中它被重命名為pg_xact |
| pg_dynshmem/ | 包含動態(tài)共享內(nèi)存子系統(tǒng)使用的文件的子目錄。版本 9.4 或更高版本。 |
| pg_logical/ | 包含用于邏輯解碼的狀態(tài)數(shù)據(jù)的子目錄。版本 9.4 或更高版本。 |
| pg_multixact/ | 包含多事務(wù)狀態(tài)數(shù)據(jù)的子目錄(用于共享行鎖) |
| pg_notify/ | 包含 LISTEN/NOTIFY 狀態(tài)數(shù)據(jù)的子目錄 |
| pg_repslot/ | 包含復制槽 數(shù)據(jù)的子目錄。版本 9.4 或更高版本。 |
| pg_serial/ | 包含有關(guān)提交的可序列化事務(wù)信息的子目錄(9.1 或更高版本) |
| pg_snapshots/ | 包含導出快照的子目錄(版本 9.2 或更高版本)。PostgreSQL 的函數(shù) pg_export_snapshot 在這個子目錄中創(chuàng)建一個快照信息文件。 |
| pg_stat/ | 包含統(tǒng)計子系統(tǒng)的永久文件的子目錄。 |
| pg_stat_tmp/ | 包含統(tǒng)計子系統(tǒng)的臨時文件的子目錄。 |
| pg_subtrans/ | 包含子事務(wù)狀態(tài)數(shù)據(jù)的子目錄 |
| pg_tblspc/ | 包含指向表空間的符號鏈接的子目錄 |
| pg_twophase/ | 包含準備交易的狀態(tài)文件的子目錄 |
| pg_wal/(版本 10 或更高版本) | 包含 WAL(預(yù)寫日志記錄)段文件的子目錄。它是從版本 10 中的pg_xlog重命名的。 |
| pg_xact/(版本 10 或更高版本) | 包含事務(wù)提交狀態(tài)數(shù)據(jù)的子目錄。它是從版本 10 中的 pg_clog 重命名的。 |
| pg_xlog/(9.6 或更早版本) | 包含 WAL(預(yù)寫日志記錄)段文件的子目錄。在版本 10 中它被重命名為pg_wal 。 |
數(shù)據(jù)庫布局
數(shù)據(jù)庫是base子目錄下的子目錄;并且數(shù)據(jù)庫目錄名稱與各自的 OID 相同。
與表和索引相關(guān)的文件布局
每個大小小于 1GB 的表或索引都是存儲在其所屬數(shù)據(jù)庫目錄下的單個文件。作為數(shù)據(jù)庫對象的表和索引在內(nèi)部由各個 OID 管理,而這些數(shù)據(jù)文件由變量relfilenode管理。表和索引的 relfilenode 值基本但并不總是與各自的 OID 匹配.
test=# SELECT relname, oid, relfilenode FROM pg_class WHERE relname = 'students';
relname | oid | relfilenode
----------+-------+-------------
students | 16393 | 16393
(1 row)
對應(yīng)目錄:
[root@localhost base]# ll ./16386/16393
-rw------- 1 omm omm 8192 May 19 02:33 ./16386/16393
也可以使用(pg9.0以上版本,opengauss都支持):
test=# select pg_relation_filepath('students');
pg_relation_filepath
----------------------
base/16386/16393
可以通過發(fā)出一些命令(例如,TRUNCATE、REINDEX、CLUSTER)來更改表和索引的 relfilenode 值。
當表和索引的文件大小超過 1GB 時,PostgreSQL 會創(chuàng)建一個名為 relfilenode.1 的新文件并使用它。如果新文件已填滿,將創(chuàng)建下一個名為 relfilenode.2 的新文件,依此類推。
仔細查看數(shù)據(jù)庫子目錄,你會發(fā)現(xiàn)每個表都有兩個關(guān)聯(lián)文件,分別以'_fsm'和'_vm'為后綴。這些被稱為空閑空間映射和可見性映射,分別存儲空閑空間容量和表格文件中每個頁面的可見性信息(參見第 5.3.4節(jié)和第 6.2節(jié)中的更多詳細信息)。索引只有單獨的可用空間圖,沒有可見性圖。它們也可以在內(nèi)部稱為每個關(guān)系的分支;可用空間映射是表/索引數(shù)據(jù)文件的第一個分支(分支編號為 1),可見性映射是表數(shù)據(jù)文件的第二個分支(分支編號為 2)。數(shù)據(jù)文件的fork號為0。
表空間
PostgreSQL 中的表空間是基本目錄之外的附加數(shù)據(jù)區(qū)域。下圖顯示了一個表空間的內(nèi)部布局,以及與主數(shù)據(jù)區(qū)的關(guān)系。

堆表文件的內(nèi)部布局
在數(shù)據(jù)文件(堆表和索引,以及空閑空間映射和可見性映射)內(nèi)部,分為固定長度的頁(或塊),默認為 8192 字節(jié)(8 KB)。每個文件中的那些頁面從 0 開始依次編號,這些編號稱為塊編號。如果文件已被填滿,PostgreSQL 會在文件末尾添加一個新的空頁以增加文件大小。

表中的一個頁面包含如下描述的三種數(shù)據(jù):
heap tuple(s) ——堆元組本身就是一個記錄數(shù)據(jù)。它們從頁面底部開始按順序堆疊。tuple 的內(nèi)部結(jié)構(gòu)此處暫不討論。
line pointer(s) – 一個行指針有 4 個字節(jié)長,并保存一個指向每個堆元組的指針。它也稱為item pointer。
行指針組成一個簡單的數(shù)組,起到元組索引的作用。每個索引從 1 開始按順序編號,稱為偏移編號。當一個新的元組被添加到頁面時,一個新的行指針也被推到數(shù)組上以指向新的。標頭數(shù)據(jù)——由結(jié)構(gòu)[PageHeaderData](javascript:void(0))定義的標頭數(shù)據(jù)分配在頁面的開頭。它長 24 字節(jié),包含有關(guān)頁面的一般信息。結(jié)構(gòu)的主要變量如下所述。
- pd_lsn——這個變量存儲了本頁最后一次更改寫入的 XLOG 記錄的 LSN。它是一個 8 字節(jié)的無符號整數(shù),與 WAL(Write-Ahead Logging)機制有關(guān)。
- pd_checksum – 此變量存儲此頁面的校驗和值。(注意9.3及以后版本支持這個變量;在早期版本中,這部分已經(jīng)存儲了頁面的timelineId。)
- pd_lower, pd_upper – pd_lower 指向行尾指針,pd_upper 指向最新堆元組的開頭。
- pd_special – 此變量用于索引。在表中的頁面中,它指向頁面的末尾。(在索引內(nèi)的頁面中,它指向特殊空間的開頭,這是僅由索引持有的數(shù)據(jù)區(qū)域,根據(jù)索引類型的種類,如B-tree、GiST、GiN等包含特定的數(shù)據(jù)。)
行尾指針和最新元組開頭之間的空白空間稱為空閑空間或空洞。
為了識別表中的元組,內(nèi)部使用了元組標識符 (TID)。TID 包含一對值:包含元組的頁面的塊號,以及指向元組的行指針的偏移量。其用法的一個典型例子是索引。
元組的讀寫方法
編寫堆元組
假設(shè)一個表由一頁組成,其中只包含一個堆元組。該頁的 pd_lower 指向第一行指針,行指針和 pd_upper 都指向第一個堆元組。見圖 1.5(a)。
當?shù)诙€元組被插入時,它被放在第一個元組之后。第二行指針被推到第一個上,它指向第二個元組。pd_lower 更改為指向第二行指針,pd_upper 更改為指向第二個堆元組。見圖 1.5(b)。此頁面中的其他標題數(shù)據(jù)(例如,pd_lsn、pg_checksum、pg_flag)也被重寫為適當?shù)闹怠?/p>

讀取堆元組
這里概述了兩種典型的訪問方法,順序掃描和 B-tree 索引掃描:
- 順序掃描——通過掃描每頁中的所有行指針順序讀取所有頁中的所有元組。見圖 1.6(a)。
- B-tree 索引掃描 ——一個索引文件包含索引元組,每個索引元組由一個索引鍵和一個指向目標堆元組的 TID 組成。如果找到了具有您要查找的鍵的索引元組,則 PostgreSQL 使用獲得的 TID 值讀取所需的堆元組。(B-tree index中查找索引元組的方法,由于很常見,篇幅有限,這里就不做說明了,見相關(guān)資料。)例如,圖1.6(b)中,TID得到的索引元組的值為'(block = 7, Offset = 2)'。這意味著目標堆元組是表內(nèi)第 7 頁中的第 2 個元組,因此 PostgreSQL 可以讀取所需的堆元組,而無需在頁面中進行不必要的掃描。
