本文介紹 PostgreSQL 數據庫的體系結構,包括實例結構(進程與內存)、存儲結構(物理與邏輯)以及插件式存儲引擎。
實例與數據庫聚簇
PostgreSQL 使用典型的客戶端/服務器(Client/Server)架構,客戶端發送請求到服務端,服務端處理完成之后返回結果到客戶端。下圖顯示了一個簡化的 PostgreSQL 體系結構:
PostgreSQL 客戶端可能是一個命令行工具、圖形工具或者 Web 服務器,它們是數據庫操作的請求者。
PostgreSQL 服務端由一個實例(Instance)以及一個數據庫聚簇(Database Cluster)組成。
實例由一組后臺進程和相關的內存組成,用于管理數據庫。啟動服務器進程時創建一個實例,關閉服務器進程時實例隨之關閉。
數據庫聚簇包含多個數據庫,一個數據庫由一組相關的對象組成,例如表、索引、視圖、存儲過程等。
提示:PostgreSQL 數據庫聚簇代表了單個主機上單個實例管理的一組數據庫,它不是由一組數據庫服務器組成的集群。
客戶端和服務端通常位于不同的主機中,它們之間通過 TCP/IP 網絡協議進行連接和通信,默認端口為 5432。當客戶端和服務端位于相同的主機時,也可以通過 Unix 域套接字進行通信。
實例結構
PostgreSQL 實例由一組進程和相關的內存組成,用于管理物理數據庫。下圖描述了實例的結構:
每當客戶端進程請求連接數據庫時,PostgreSQL 服務進程(postgres)負責監聽連接請求并驗證客戶端信息。驗證通過之后主服務進程會為每一個客戶端連接創建一個新的后端進程,然后客戶端進程和對應的后端進程(postgres)直接通信,后端進程代表客戶端執行各種數據庫操作并返回結果。
數據庫進程
PostgreSQL?服務進程(server process)是其他進程的父進程,當我們啟動 PostgreSQL 服務時首先會創建該進程,然后它再分配共享內存(shared memory)并啟動各種后臺進程。服務進程還負責監聽客戶端連接請求,默認監聽端口為 5432。在早期版本中,PostgreSQL 服務進程也被稱為 postmaster。
PostgreSQL?后端進程(backend process)用于代表客戶端執行數據庫操作,服務進程在驗證客戶端連接請求后為其創建新的后端進程,在客戶端斷開連接時終止對應的后端進程。
提示:PostgreSQL 使用類似 Oracle 中的專用服務器模式,每個客戶端進程連接到一個專用的后端進程。后端進程在客戶端會話期間不被任何其他客戶端共享。每個新的會話都會分配一個專用的后端進程。
PostgreSQL 支持的最大客戶端連接數由系統參數 max_connections 控制,默認值為 100。
PostgreSQL?后臺進程(background processes)用于執行各種數據庫管理操作,主要包括:
后臺寫進程(background writer),負責定期將共享緩沖池(shared buffer pool)中的臟頁寫入磁盤。
檢查點進程(checkpointer),發生檢查點時負責將檢查點記錄寫入 WAL 段文件,同時刷新共享緩沖池中的臟頁。
自動清理啟動進程(autovacuum launcher),定期調用工作進程(autovacuum worker)完成清理工作。
WAL 寫進程(wal writer),定期將 WAL 緩沖數據寫入 WAL 段文件。
統計收集進程(stats collector),收集數據庫系統運行的統計信息,PostgreSQL 15 版本優化刪除。
歸檔進程(archiver),負責 WAL 段文件歸檔,也就是日志歸檔。
日志收集進程(logger),記錄數據庫錯誤日志。
除了以上進程之外,PostgreSQL 還會基于不同配置啟動其他后臺進程。例如,流復制相關的 walsender(發送 WAL 數據)以及 walreceiver(接收 WAL 數據)進程,用戶自定義實現的后臺工作進程(Background Worker Process)等。
后臺寫進程
后臺寫進程負責定期將共享緩沖池中的臟頁寫入磁盤,默認情況下每隔 200 毫秒(bgwriter_delay)刷新一次磁盤,每次最多刷新 100 個緩沖頁(bgwriter_lru_maxpages)。這種刷新臟頁的方式可以盡量減少對數據庫性能的影響。
另外,PostgreSQL 9.1 版本之前,后臺寫進程還需要負責檢查點過程。PostgreSQL 9.2 開始,單獨的檢查點進程開始負責這部分工作。
檢查點進程
檢查點進程負責檢查點(Checkpoint)操作,也就是將檢查點記錄寫入 WAL 段文件并且刷新共享緩沖池中的全部臟頁。
以下情況都會觸發檢查點操作:
距離上一次檢查點操作的時間間隔到達參數 checkpoint_timeout 的配置,默認為 300 秒。
PostgreSQL 9.4 以及更低版本,上一次檢查點操作之后寫入的 WAL 段文件數據到達參數 checkpoint_segments 的配置,默認值為 3。
PostgreSQL 9.5 以及更高版本,pg_wal(pg_xlog)目錄中 WAL 段文件總大小超過參數 max_wal_size 的配置,默認值為 1 GB。
PostgreSQL 數據庫服務以 smart(SIGTERM)或者 fast(SIGINT)模式關閉。
超級用戶或者 pg_checkpoint 特權用戶手動執行 CHECKPOINT 命令。
檢查點機制可以將內存中的臟數據刷新到磁盤,并且生成一個一致性的數據庫狀態。系統崩潰后,執行數據庫恢復時可以從最近的檢查點開始,優化恢復性能。
自動清理啟動進程
自動清理啟動進程負責定期調用工作進程執行清理流程(VACUUM 以及 ANALYZE),默認情況下每隔 1 分鐘(autovacuum_naptime)運行一次,每次最多調用 3 個(autovacuum_max_workers)工作進程。
WAL 寫進程
WAL 寫進程負責定期將 WAL 緩沖數據(XLOG)寫入 WAL 段文件,這些文件位于 pg_wal 子目錄中。
WAL 寫進程默認 200 毫秒(wal_writer_delay)刷新一次緩沖,可以避免一次提交大量數據時的磁盤寫入瓶頸。
統計收集進程
PostgreSQL 14 以及之前的版本中存在統計收集進程,負責收集系統運行時的統計信息,并且通過 pg_stat_activity 等動態視圖提供數據。
PostgreSQL 15 版本開始使用累積統計系統,基于共享內存存儲統計信息,優化了性能,同時刪除了獨立的統計收集進程。
歸檔進程
歸檔進程負責 WAL 段文件的連續歸檔,在發生 WAL 段切換時將其復制到歸檔區域。日志歸檔功能可以用于物理熱備以及即時點恢復(PITR)。
如果想要啟動 WAL 歸檔,需要將配置參數 wal_level 設置為 replica 或者更高級別,同時將配置參數 archive_mode 設置為 on,然后在 archive_command 參數中設置歸檔命令或者在 archive_library 參數中指定歸檔模塊。
日志收集進程
日志收集進程負責將錯誤信息記錄到錯誤日志文件,該進程由配置參數 logging_collector 控制,默認設置為 on。
內存結構
PostgreSQL 實例的內存結構可以分為以下兩個大類:
共享內存區
PostgreSQL 實例啟動時分配共享內存區,它是所有服務端進程共享的內存區,具體可以分為共享緩沖池(shared buffer pool)、WAL 緩沖(WAL buffer)、提交日志緩沖(CLOG)等。
共享緩沖池用于加載磁盤中的表和索引數據,并且在內存中進行操作,從而減少磁盤 I/O,提高性能。共享緩沖池的大小通過參數 shared_buffers 進行配置,默認值為 128 MB。
雖然對于 shared_buffers 沒有具體的推薦值,但是可以針對具體的系統計算出一個大概的值。一般來說,對于專用的數據庫服務器,shared_buffers 大概可以設置為系統內存的 25%。增加 shared_buffers 的值通常可以提高性能,例如,當整個數據庫都可以被加載到緩存中時,可以明顯減少磁盤的讀取操作。由于 PostgreSQL 還依賴于操作系統的緩存,大于內存 40% 的 shared_buffers 并不會帶來性能的提示,反而可能會下降。
另外,增加 shared_buffers 的值通常也需要相應地增加 max_wal_size 的值,以便延長檢查點的時間間隔。
PostgreSQL 使用預寫日志(WAL)確保數據的持久性;與 shared_buffers 作用類似,PostgreSQL 將 WAL 日志(XLOG)寫入緩沖并且批量寫入磁盤。
默認的 WAL 緩沖大小由 wal_buffers 參數進行設置,初始值為 4MB(shared_buffers 的 1/32)。WAL 緩沖區在每次事務提交時都會寫入磁盤,因此過大的值并不會帶來顯著的性能提升。不過,對于大量并發的寫入操作,適當增加該參數的值可以提高系統的性能。
CLOG 緩沖存儲了每個事務的狀態(IN_PROGRESS、COMMITTED、ABORTED 以及 SUB_COMMITTED),用戶事務管理和并發控制。當 PostgreSQL 關閉服務或者執行檢查點過程時,會將 CLOG 數據寫入 pg_xact(pg_clog)子目錄文件中;當 PostgreSQL 服務啟動時,會從文件中加載初始 CLOG。
共享內存區還包括許多其他子區,例如用于實現各種訪問控制機制(信號量、輕量級鎖、共享鎖、排他鎖等)的內存,各種后臺進程(checkpointer、autovacuum 等)使用的內存,事務處理(保存點、兩階段提交等)所需的內存。
本地內存區
本地內存區是為每個后端進程動態分配的獨享內存區,用于執行查詢處理、數據排序、哈希連接等操作,以及存儲臨時表和會話級別的數據。
本地內存區主要包括工作內存(work_mem)、維護工作內存(maintenance_work_mem)以及臨時緩沖(temp_buffers)。
工作內存用于查詢處理過程中的數據排序(ORDER BY、DISTINCT)以及表之間的連接(Hash Join、Sort Merge Join)等操作。工作內存由參數 work_mem 進行配置,默認為 4 MB。
如果設置了合適的 work_mem,大部分的排序操作都在內存中執行,而不需要使用磁盤存儲臨時結果。對于復雜的查詢,可能會執行并發的排序或者哈希操作,每個操作都可以最多使用該參數設置的內存。另外,多個會話可能同時執行排序操作。因此,排序占用的總內存可能是 work_mem 的許多倍;work_mem 的值不能設置的過高,因為它可能導致內存使用瓶頸。
維護工作內存主要用于數據庫維護操作,例如 VACUUM、CREATE INDEX 以及 ALTER TABLE ADD FOREIGN KEY 等操作。這些操作在執行時可能需要較大的內存空間來優化性能。
配置參數 maintenance_work_mem 指定了維護工作內存的大小,默認值為 64 MB。由于一個數據庫會話同時只能執行一個維護操作,一般不會存在并發的維護操作;所以將該參數設置的比 work_mem 大很多也不會有問題,更大的維護內存還能夠提高數據庫清理和數據導入的性能。
臨時緩沖用于存儲臨時表數據。每個會話都可以使用單獨的臨時緩沖來存儲臨時表的數據,以提高訪問效率。
配置參數 temp_buffers 用于設置臨時緩沖的大小,默認為 8MB。
存儲結構
一個 PostgreSQL 實例管理一個數據庫聚簇,它可以包含多個數據庫。這里的聚簇不是多臺服務器組成的集群。
物理存儲
一個數據庫聚簇通常對應操作系統中的一個目錄,也就是根目錄(PGDATA)。使用 SHOW 命令查看如下:
SHOW data_directory;
data_directory ? ? ? ? ? ? ? ? ? ? |
-----------------------------------+
D:/Program Files/PostgreSQL/17/data|
根目錄包含多個子目錄和文件:
PS D:\Program Files\PostgreSQL\17\data> ls
Mode ? ? ? ? ? ? ? ? LastWriteTime ? ? ? ? Length Name
---- ? ? ? ? ? ? ? ? ------------- ? ? ? ? ------ ----
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?base
d----- ? ? ? ? 2024/7/11 ? ? 14:35 ? ? ? ? ? ? ? ?global
d----- ? ? ? ? 2024/7/22 ? ? 17:08 ? ? ? ? ? ? ? ?log
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_commit_ts
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_dynshmem
d----- ? ? ? ? 2024/7/11 ? ? 14:39 ? ? ? ? ? ? ? ?pg_logical
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_multixact
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_notify
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_replslot
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_serial
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_snapshots
d----- ? ? ? ? 2024/7/11 ? ? 14:34 ? ? ? ? ? ? ? ?pg_stat
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_stat_tmp
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_subtrans
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_tblspc
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_twophase
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_wal
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?pg_xact
-a---- ? ? ? ? 2024/7/23 ? ? 17:16 ? ? ? ? ? ? 45 current_logfiles
-a---- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? 5639 pg_hba.conf
-a---- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? 2712 pg_ident.conf
-a---- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ?3 PG_VERSION
-a---- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? 90 postgresql.auto.conf
-a---- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ?31602 postgresql.conf
-a---- ? ? ? ? 2024/7/11 ? ? 14:34 ? ? ? ? ? ? 91 postmaster.opts
-a---- ? ? ? ? 2024/7/11 ? ? 14:34 ? ? ? ? ? ? 70 postmaster.pid
下表列出了這些目錄和文件的描述。
項目 | 類型 | 描述 |
---|
base | 目錄 | 包含多個子目錄,每個目錄對應一個數據庫。 |
global | 目錄 | 存儲全局(聚簇)級別的數據表,例如 pg_database 等。控制文件也存儲在這個目錄中。 |
log | 目錄 | 存儲服務器日志信息。 |
pg_commit_ts | 目錄 | 保存事務提交時間戳數據。 |
pg_dynshmem | 目錄 | 存儲動態共享內存子系統使用的文件。 |
pg_logical | 目錄 | 存儲邏輯解碼狀態數據。 |
pg_multixact | 目錄 | 存儲用于共享行鎖的多事務(multitransaction)狀態數據。 |
pg_notify | 目錄 | 存儲 LISTEN/NOTIFY(消息通知機制)狀態數據。 |
pg_replslot | 目錄 | 存儲復制槽數據。 |
pg_serial | 目錄 | 存儲已提交的串行化事務信息。 |
pg_snapshots | 目錄 | 存儲導出的快照。 |
pg_stat | 目錄 | 存儲統計子系統使用的持久化文件。 |
pg_stat_tmp | 目錄 | 存儲統計子系統使用的臨時文件。 |
pg_subtrans | 目錄 | 存儲子事務狀態數據。 |
pg_tblspc | 目錄 | 存儲表空間目錄的符號鏈接。 |
pg_twophase | 目錄 | 存儲預備事務(兩階段提交)的狀態文件。 |
pg_wal | 目錄 | 存儲預寫式日志(WAL)文件。 |
pg_xact | 目錄 | 存儲事務提交狀態數據。 |
current_logfiles | 文件 | 記錄當前寫入的服務器日志文件。 |
pg_hba.conf | 文件 | 客戶端認證配置文件。 |
pg_ident.conf | 文件 | 用戶名映射文件。 |
PG_VERSION | 文件 | 記錄 PostgreSQL 主版本號。 |
postgresql.auto.conf | 文件 | 存儲使用 ALTER SYSTEM 命令設置的參數信息。 |
postgresql.conf | 文件 | 主配置參數文件。 |
postmaster.opts | 文件 | 記錄服務器上次啟動時使用的命令行選項。 |
postmaster.pid | 文件 | 記錄主服務進程 ID,數據庫聚簇根目錄,主服務進程啟動時間戳,服務端口、Unix 域套接字目錄(可空),第一個有效的監聽地址以及共享內存段 ID 等信息。該文件在服務啟動時創建,服務停止時刪除。 |
其中,base 子目錄存儲了每個數據庫的數據文件和索引文件等內容:
PS D:\Program Files\PostgreSQL\17\data\base> ls
Mode ? ? ? ? ? ? ? ? LastWriteTime ? ? ? ? Length Name
---- ? ? ? ? ? ? ? ? ------------- ? ? ? ? ------ ----
d----- ? ? ? ? 2024/6/20 ? ? ?9:09 ? ? ? ? ? ? ? ?1
d----- ? ? ? ? 2024/6/17 ? ? 10:44 ? ? ? ? ? ? ? ?4
d----- ? ? ? ? 2024/7/11 ? ? 14:35 ? ? ? ? ? ? ? ?5
每個數字子目錄對應一個數據庫的標識符(OID),使用 SQL 查詢數據庫信息如下:
select oid, datname
from pg_database;
oid|datname ?|
---+---------+
?5|postgres |
?1|template1|
?4|template0|
數據庫中的對象存儲在各自的子目錄中,pg_relation_filepath 函數可以用于查詢對象的文件路徑(相對于根目錄):
SELECT pg_relation_filepath('public.animal');
pg_relation_filepath|
--------------------+
base/5/73734 ? ? ? ?|
animal 是數據庫 postgres 中的一個表,它的 OID 為 73734,數據文件為根目錄下的 base/5/73734。
表空間
在 PostgreSQL 中,表空間(tablespace)表示數據文件的存放目錄,這些數據文件代表了數據庫的對象,例如表或索引。創建數據庫對象時,只需要指定存儲對象的表空間的名稱(或者使用默認值),而不需要指定磁盤上的物理路徑。當我們訪問表時,系統通過它所在的表空間定位到對應數據文件所在的位置。
PostgreSQL 中的表空間與其他數據庫系統不太一樣,它更偏向于一個物理上的概念。
表空間的引入為 PostgreSQL 的管理帶來了以下好處:
PostgreSQL 在集群初始化時將默認創建了兩個表空間:
創建表和索引時的默認表空間使用參數 default_tablespace 進行配置。使用 CREATE 命令指定表空間的語法如下:
CREATE TABLE ...
TABLESPACE ts_name;
邏輯存儲
一個數據庫聚簇包含多個數據庫。數據庫由一組相關的對象組成,例如表、索引、視圖、存儲過程等。數據庫中的對象使用模式(Schema)進行邏輯組織。準確地說,一個數據庫由多個模式組成,模式由許多對象組成。
PostgreSQL 的邏輯存儲結構如下圖所示:
多個數據庫之間是物理隔離的,每個數據庫在 PostgreSQL 中都對應一個獨立的目錄,其中包含該數據庫的所有數據文件和元數據。客戶端連接服務器時需要指定數據庫名稱,連接到一個數據庫的客戶端無法查詢另一個數據庫中的數據,除非使用外部數據封裝器(FDW)。
一個數據庫中的多個模式之間是邏輯隔離的,不同模式中可以存在同名的對象,例如 schema1 和 schema2 中都可以存在名為 test 的數據表。PostgreSQL 權限管理系統控制模式對象的訪問,訪問對象時可以包含模式名稱,例如 schema1.test。
每個數據庫對象都有一個唯一的標識符(OID),它是一個無符號的四字節整數。這些標識符用于在系統表中唯一標識不同的數據庫對象。例如,數據庫的 OID 存儲在 pg_database 表中,模式的 OID 存儲在 pg_namespace 表中,關系(表、索引、序列、視圖、復合類型等)的 OID 存儲在 pg_class 表中。通過這些標識符,PostgreSQL 能夠在內部有效地管理和引用各種數據庫對象。
存儲引擎
PostgreSQL 12 開始支持插件式表訪問方法(Table Access Method),基于這個接口可以實現不同的數據存儲引擎,針對特定的工作負載定制數據的存儲和檢索方式,從而提高系統的整體性能。
默認的數據存儲引擎為 heap(堆表),使用參數 default_table_access_method 進行設置:
SHOW default_table_access_method;
default_table_access_method|
---------------------------+
heap ? ? ? ? ? ? ? ? ? ? ? |
用戶創建表或者物化視圖時可以指定存儲引擎,語法如下:
CREATE TABLE ...
USING method;
除了默認的 heap 之外,已知正在開發的存儲引擎包括 columnar 和 OrioleDB 等。
PostgreSQL 也支持插件式索引訪問方法(Index Access Method),并且基于這個接口實現了 B-Tree、Hash、GiST、GIN 等不同的索引類型,同時用戶也可以擴展自定義的索引類型,從而優化不同場景下的查詢性能。
該文章在 2024/7/30 18:46:28 編輯過