2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2

27.5. 動態跟蹤 #

PostgreSQL 提供了支援資料庫伺服器動態跟蹤的設施。這允許外部實用程式在程式碼的特定點被呼叫,從而跟蹤執行。

原始碼中已經插入了許多探測點或跟蹤點。這些探測點供資料庫開發人員和管理員使用。預設情況下,探測點不會編譯到 PostgreSQL 中;使用者需要顯式告訴 configure 指令碼使探測點可用。

目前支援 DTrace 實用程式,該實用程式在撰寫本文時可用於 Solaris、macOS、FreeBSD、NetBSD 和 Oracle Linux。Linux 的 SystemTap 專案提供了 DTrace 的等效功能,也可以使用。理論上,透過修改 src/include/utils/probes.h 中的宏定義,可以支援其他動態跟蹤實用程式。

27.5.1. 編譯動態跟蹤 #

預設情況下,探測點不可用,因此您需要顯式告訴 configure 指令碼在 PostgreSQL 中使探測點可用。要包含 DTrace 支援,請在 configure 時指定 --enable-dtrace。有關更多資訊,請參見 第 17.3.3.6 節

27.5.2. 內建探測點 #

原始碼中提供了許多標準探測點,如表 27.49 所示;表 27.50 顯示了探測點中使用的型別。當然可以新增更多探測點來增強 PostgreSQL 的可觀測性。

表 27.49. 內建 DTrace 探測點

名稱 引數 描述
transaction-start (LocalTransactionId) 在新的事務開始時觸發的探測點。arg0 是事務 ID。
transaction-commit (LocalTransactionId) 事務成功完成時觸發的探測點。arg0 是事務 ID。
transaction-abort (LocalTransactionId) 事務不成功完成時觸發的探測點。arg0 是事務 ID。
query-start (const char *) 查詢處理開始時觸發的探測點。arg0 是查詢字串。
query-done (const char *) 查詢處理完成時觸發的探測點。arg0 是查詢字串。
query-parse-start (const char *) 查詢解析開始時觸發的探測點。arg0 是查詢字串。
query-parse-done (const char *) 查詢解析完成時觸發的探測點。arg0 是查詢字串。
query-rewrite-start (const char *) 查詢重寫開始時觸發的探測點。arg0 是查詢字串。
query-rewrite-done (const char *) 查詢重寫完成時觸發的探測點。arg0 是查詢字串。
query-plan-start () 查詢規劃開始時觸發的探測點。
query-plan-done () 查詢規劃完成時觸發的探測點。
query-execute-start () 查詢執行開始時觸發的探測點。
query-execute-done () 查詢執行完成時觸發的探測點。
statement-status (const char *) 伺服器程序更新其 pg_stat_activity.status 時觸發的探測點。arg0 是新的狀態字串。
checkpoint-start (int) 檢查點開始時觸發的探測點。arg0 包含用於區分不同檢查點型別的位標誌,例如關閉、立即或強制。
checkpoint-done (int, int, int, int, int) 檢查點完成時觸發的探測點。(接下來的探測點在檢查點處理過程中按順序觸發。)arg0 是寫入的緩衝區數量。arg1 是緩衝區的總數。arg2、arg3 和 arg4 分別包含新增、刪除和回收的 WAL 檔案數量。
clog-checkpoint-start (bool) 檢查點 CLOG 部分開始時觸發的探測點。arg0 對於正常檢查點為 true,對於關閉檢查點為 false。
clog-checkpoint-done (bool) 檢查點 CLOG 部分完成時觸發的探測點。arg0 的含義與 clog-checkpoint-start 相同。
subtrans-checkpoint-start (bool) 檢查點 SUBTRANS 部分開始時觸發的探測點。arg0 對於正常檢查點為 true,對於關閉檢查點為 false。
subtrans-checkpoint-done (bool) 檢查點 SUBTRANS 部分完成時觸發的探測點。arg0 的含義與 subtrans-checkpoint-start 相同。
multixact-checkpoint-start (bool) 檢查點 MultiXact 部分開始時觸發的探測點。arg0 對於正常檢查點為 true,對於關閉檢查點為 false。
multixact-checkpoint-done (bool) 檢查點 MultiXact 部分完成時觸發的探測點。arg0 的含義與 multixact-checkpoint-start 相同。
buffer-checkpoint-start (int) 檢查點緩衝區寫入部分開始時觸發的探測點。arg0 包含用於區分不同檢查點型別的位標誌,例如關閉、立即或強制。
buffer-sync-start (int, int) 在檢查點期間開始寫入髒緩衝區時觸發的探測點(在確定哪些緩衝區需要寫入後)。arg0 是緩衝區的總數。arg1 是當前髒汙並需要寫入的緩衝區數量。
buffer-sync-written (int) 檢查點期間每次寫入緩衝區後觸發的探測點。arg0 是緩衝區的 ID 號。
buffer-sync-done (int, int, int) 所有髒緩衝區都已寫入後觸發的探測點。arg0 是緩衝區的總數。arg1 是檢查點程序實際寫入的緩衝區數量。arg2 是預期寫入的數量(buffer-sync-start 的 arg1);任何差異都反映了其他程序在檢查點期間重新整理緩衝區。
buffer-checkpoint-sync-start () 在將髒緩衝區寫入核心後,開始發出 fsync 請求之前觸發的探測點。
buffer-checkpoint-done () 緩衝區與磁碟同步完成時觸發的探測點。
twophase-checkpoint-start () 檢查點兩階段部分開始時觸發的探測點。
twophase-checkpoint-done () 檢查點兩階段部分完成時觸發的探測點。
buffer-extend-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int, unsigned int) 關係擴充套件開始時觸發的探測點。arg0 包含要擴充套件的 fork。arg1、arg2 和 arg3 包含標識關係的表空間、資料庫和關係 OID。arg4 是建立本地緩衝區臨時關係(或 INVALID_PROC_NUMBER (-1) 用於共享緩衝區)的後端 ID。arg5 是呼叫者希望擴充套件的塊數。
buffer-extend-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, unsigned int, BlockNumber) 關係擴充套件完成時觸發的探測點。arg0 包含要擴充套件的 fork。arg1、arg2 和 arg3 包含標識關係的表空間、資料庫和關係 OID。arg4 是建立本地緩衝區臨時關係(或 INVALID_PROC_NUMBER (-1) 用於共享緩衝區)的後端 ID。arg5 是關係擴充套件的塊數,由於資源限制,這可能小於 buffer-extend-start 中的數量。arg6 包含第一個新塊的 BlockNumber。
buffer-read-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int) 開始讀取緩衝區時觸發的探測點。arg0 和 arg1 包含頁的 fork 和塊號。arg2、arg3 和 arg4 包含標識關係的表空間、資料庫和關係 OID。arg5 是建立本地緩衝區臨時關係(或 INVALID_PROC_NUMBER (-1) 用於共享緩衝區)的後端 ID。
buffer-read-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool) 讀取緩衝區完成時觸發的探測點。arg0 和 arg1 包含頁的 fork 和塊號。arg2、arg3 和 arg4 包含標識關係的表空間、資料庫和關係 OID。arg5 是建立本地緩衝區臨時關係(或 INVALID_PROC_NUMBER (-1) 用於共享緩衝區)的後端 ID。arg6 如果緩衝區在池中找到則為 true,否則為 false。
buffer-flush-start (ForkNumber, BlockNumber, Oid, Oid, Oid) 在發出共享緩衝區的任何寫入請求之前觸發的探測點。arg0 和 arg1 包含頁的 fork 和塊號。arg2、arg3 和 arg4 包含標識關係的表空間、資料庫和關係 OID。
buffer-flush-done (ForkNumber, BlockNumber, Oid, Oid, Oid) 寫入請求完成時觸發的探測點。(注意,這僅反映了將資料傳遞給核心的時間;通常尚未實際寫入磁碟。)引數與 buffer-flush-start 相同。
wal-buffer-write-dirty-start () 當伺服器程序開始寫入髒 WAL 緩衝區,因為沒有更多 WAL 緩衝區空間可用時觸發的探測點。(如果經常發生這種情況,則表明 wal_buffers 太小。)
wal-buffer-write-dirty-done () 髒 WAL 緩衝區寫入完成時觸發的探測點。
wal-insert (unsigned char, unsigned char) 插入 WAL 記錄時觸發的探測點。arg0 是記錄的資源管理器(rmid)。arg1 包含資訊標誌。
wal-switch () 請求 WAL 段切換時觸發的探測點。
smgr-md-read-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int) 開始從關係中讀取塊時觸發的探測點。arg0 和 arg1 包含頁的 fork 和塊號。arg2、arg3 和 arg4 包含標識關係的表空間、資料庫和關係 OID。arg5 是建立本地緩衝區臨時關係(或 INVALID_PROC_NUMBER (-1) 用於共享緩衝區)的後端 ID。
smgr-md-read-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) 讀取塊完成時觸發的探測點。arg0 和 arg1 包含頁的 fork 和塊號。arg2、arg3 和 arg4 包含標識關係的表空間、資料庫和關係 OID。arg5 是建立本地緩衝區臨時關係(或 INVALID_PROC_NUMBER (-1) 用於共享緩衝區)的後端 ID。arg6 是實際讀取的位元組數,而 arg7 是請求的位元組數(如果它們不同,則表示讀取不足)。
smgr-md-write-start (ForkNumber, BlockNumber, Oid, Oid, Oid, int) 開始將塊寫入關係時觸發的探測點。arg0 和 arg1 包含頁的 fork 和塊號。arg2、arg3 和 arg4 包含標識關係的表空間、資料庫和關係 OID。arg5 是建立本地緩衝區臨時關係(或 INVALID_PROC_NUMBER (-1) 用於共享緩衝區)的後端 ID。
smgr-md-write-done (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) 寫入塊完成時觸發的探測點。arg0 和 arg1 包含頁的 fork 和塊號。arg2、arg3 和 arg4 包含標識關係的表空間、資料庫和關係 OID。arg5 是建立本地緩衝區臨時關係(或 INVALID_PROC_NUMBER (-1) 用於共享緩衝區)的後端 ID。arg6 是實際寫入的位元組數,而 arg7 是請求的位元組數(如果它們不同,則表示寫入不足)。
sort-start (int, bool, int, int, bool, int) 排序操作開始時觸發的探測點。arg0 指示堆、索引或基數排序。arg1 對於唯一值強制為 true。arg2 是鍵列的數量。arg3 是允許的工作記憶體的千位元組數。arg4 如果需要對排序結果進行隨機訪問,則為 true。arg5 指示序列(0)、並行工作程序(1)或並行領導者(2)。
sort-done (bool, long) 排序完成時觸發的探測點。arg0 對於外部排序為 true,對於內部排序為 false。arg1 是外部排序使用的磁碟塊數,或內部排序使用的記憶體千位元組數。
lwlock-acquire (char *, LWLockMode) 獲取 LWLock 時觸發的探測點。arg0 是 LWLock 的分片。arg1 是請求的鎖模式,可以是獨佔或共享。
lwlock-release (char *) 釋放 LWLock 時觸發的探測點(但請注意,任何已釋放的等待者尚未被喚醒)。arg0 是 LWLock 的分片。
lwlock-wait-start (char *, LWLockMode) LWLock 未立即可用,並且伺服器程序已開始等待鎖可用時觸發的探測點。arg0 是 LWLock 的分片。arg1 是請求的鎖模式,可以是獨佔或共享。
lwlock-wait-done (char *, LWLockMode) 伺服器程序從等待 LWLock 中釋放時觸發的探測點(尚未實際獲得鎖)。arg0 是 LWLock 的分片。arg1 是請求的鎖模式,可以是獨佔或共享。
lwlock-condacquire (char *, LWLockMode) 當呼叫者指定不等待時,成功獲取 LWLock 時觸發的探測點。arg0 是 LWLock 的分片。arg1 是請求的鎖模式,可以是獨佔或共享。
lwlock-condacquire-fail (char *, LWLockMode) 當呼叫者指定不等待時,未能成功獲取 LWLock 時觸發的探測點。arg0 是 LWLock 的分片。arg1 是請求的鎖模式,可以是獨佔或共享。
lock-wait-start (unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) 由於鎖不可用,重鎖(lmgr lock)請求開始等待時觸發的探測點。arg0 到 arg3 是標識被鎖定物件的標籤欄位。arg4 指示被鎖定物件的型別。arg5 指示請求的鎖型別。
lock-wait-done (unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) 重鎖(lmgr lock)請求完成等待(即已獲得鎖)時觸發的探測點。引數與 lock-wait-start 相同。
deadlock-found () 死鎖檢測器發現死鎖時觸發的探測點。

表 27.50. 探測點引數中使用的定義型別

型別 定義
LocalTransactionId unsigned int
LWLockMode int
LOCKMODE int
BlockNumber unsigned int
Oid unsigned int
ForkNumber int
bool unsigned char

27.5.3. 使用探測點 #

下面的示例顯示了一個 DTrace 指令碼,用於分析系統中事務的計數,作為快照 pg_stat_database 在效能測試前後的一種替代方法。

#!/usr/sbin/dtrace -qs

postgresql$1:::transaction-start
{
      @start["Start"] = count();
      self->ts  = timestamp;
}

postgresql$1:::transaction-abort
{
      @abort["Abort"] = count();
}

postgresql$1:::transaction-commit
/self->ts/
{
      @commit["Commit"] = count();
      @time["Total time (ns)"] = sum(timestamp - self->ts);
      self->ts=0;
}

當執行時,示例 D 指令碼會產生類似以下的輸出

# ./txn_count.d `pgrep -n postgres` or ./txn_count.d <PID>
^C

Start                                          71
Commit                                         70
Total time (ns)                        2312105013

注意

SystemTap 使用與 DTrace 不同的跟蹤指令碼表示法,儘管底層跟蹤點是相容的。值得注意的一點是,在撰寫本文時,SystemTap 指令碼必須使用雙下劃線而不是連字元來引用探測點名稱。預計這將在未來的 SystemTap 版本中得到修復。

您應該記住,DTrace 指令碼需要仔細編寫和除錯,否則收集到的跟蹤資訊可能毫無意義。在大多數發現問題的情況下,是儀器化出了問題,而不是底層系統。在討論使用動態跟蹤找到的資訊時,請務必附上用於允許檢查和討論指令碼的指令碼。

27.5.4. 定義新探測點 #

開發人員可以在程式碼中的任何需要的地方定義新探測點,但這將需要重新編譯。以下是插入新探測點的步驟:

  1. 確定探測點名稱和透過探測點提供的資料

  2. 將探測點定義新增到 src/backend/utils/probes.d

  3. 如果包含 pg_trace.h 的模組中尚未包含它,請包含它,並在原始碼中所需的點插入 TRACE_POSTGRESQL 探測點宏。

  4. 重新編譯並驗證新探測點是否可用。

示例:  以下是如何新增一個探測點來透過事務 ID 跟蹤所有新事務的示例。

  1. 確定探測點將命名為 transaction-start,並且需要 LocalTransactionId 型別的引數。

  2. 將探測點定義新增到 src/backend/utils/probes.d

    probe transaction__start(LocalTransactionId);
    

    注意探測點名稱中使用雙下劃線。在使用該探測點的 DTrace 指令碼中,雙下劃線需要替換為連字元,因此 transaction-start 是要向用戶記錄的名稱。

  3. 在編譯時,transaction__start 會被轉換為一個名為 TRACE_POSTGRESQL_TRANSACTION_START 的宏(請注意這裡的下劃線是單的),透過包含 pg_trace.h 即可使用。將宏呼叫新增到原始碼中的相應位置。在這種情況下,它看起來像這樣:

    TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
    
  4. 重新編譯並執行新二進位制檔案後,透過執行以下 DTrace 命令檢查新新增的探測點是否可用。您應該看到類似以下的輸出:

    # dtrace -ln transaction-start
       ID    PROVIDER          MODULE           FUNCTION NAME
    18705 postgresql49878     postgres     StartTransactionCommand transaction-start
    18755 postgresql49877     postgres     StartTransactionCommand transaction-start
    18805 postgresql49876     postgres     StartTransactionCommand transaction-start
    18855 postgresql49875     postgres     StartTransactionCommand transaction-start
    18986 postgresql49873     postgres     StartTransactionCommand transaction-start
    

在 C 程式碼中新增跟蹤宏時,有幾點需要注意:

  • 您應確保為探測點引數指定的資料型別與宏中使用的變數的資料型別匹配。否則,您將收到編譯錯誤。

  • 在大多數平臺上,如果 PostgreSQL 使用 --enable-dtrace 構建,那麼每當控制流經過宏時,都會評估跟蹤宏的引數,即使沒有進行跟蹤。如果您只是報告幾個區域性變數的值,這通常不值得擔心。但請注意不要將昂貴的函式呼叫放入引數中。如果您需要這樣做,請考慮使用一個檢查來保護宏,以檢視跟蹤是否真的已啟用。

    if (TRACE_POSTGRESQL_TRANSACTION_START_ENABLED())
        TRACE_POSTGRESQL_TRANSACTION_START(some_function(...));
    

    每個跟蹤宏都有一個對應的 ENABLED 宏。

提交更正

如果您在文件中發現任何不正確之處、與您對特定功能的實際體驗不符之處,或需要進一步澄清的地方,請使用 此表單 報告文件問題。