支援的版本:目前 (17) / 16 / 15 / 14
開發版本:devel

32.5. 管線模式 #

libpq 管線模式允許應用程式傳送查詢,而無需讀取先前傳送的查詢結果。利用管線模式,客戶端等待伺服器的時間會減少,因為可以在單個網路事務中傳送/接收多個查詢/結果。

雖然管線模式提供了顯著的效能提升,但使用管線模式編寫客戶端更複雜,因為它涉及管理待處理查詢的佇列,並找到哪個結果對應於佇列中的哪個查詢。

管線模式通常也會消耗客戶端和伺服器上更多的記憶體,但仔細且積極地管理傳送/接收佇列可以減輕這種情況。無論連線處於封鎖模式還是非封鎖模式,都適用此情況。

雖然 libpq 的管線 API 是在 PostgreSQL 14 中引入的,但它是一個客戶端功能,不需要特殊的伺服器支援,並且適用於任何支援 v3 擴充查詢協定的伺服器。有關更多資訊,請參閱第 53.2.4 節

32.5.1. 使用管線模式 #

要發布管線,應用程式必須將連線切換到管線模式,這可以使用 PQenterPipelineMode 完成。PQpipelineStatus 可用於測試管線模式是否處於活動狀態。在管線模式下,僅允許使用擴充查詢協定的非同步操作,不允許包含多個 SQL 命令的命令字串,也不允許 COPY。使用同步命令執行函數(例如 PQfnPQexecPQexecParamsPQpreparePQexecPreparedPQdescribePreparedPQdescribePortalPQclosePreparedPQclosePortal)是一種錯誤情況。PQsendQuery 也不允許,因為它使用簡單查詢協定。一旦所有已分派的命令都已處理其結果,並且已消耗結束管線結果,應用程式可以使用 PQexitPipelineMode 返回到非管線模式。

注意

最好在 非封鎖模式 下使用 libpq 的管線模式。如果在封鎖模式下使用,則可能發生客戶端/伺服器死鎖。[15]

32.5.1.1. 發布查詢 #

進入管線模式後,應用程式使用 PQsendQueryParams 或其預處理查詢同級 PQsendQueryPrepared 分派請求。這些請求在客戶端排隊,直到刷新到伺服器;當使用 PQpipelineSync 在管線中建立同步點,或者當呼叫 PQflush 時,會發生這種情況。函數 PQsendPreparePQsendDescribePreparedPQsendDescribePortalPQsendClosePreparedPQsendClosePortal 也在管線模式下工作。結果處理如下所述。

伺服器按照客戶端傳送的順序執行語句並傳回結果。伺服器將立即開始執行管線中的命令,而無需等待管線結束。請注意,結果緩衝在伺服器端;當使用 PQpipelineSyncPQsendPipelineSync 建立同步點,或呼叫 PQsendFlushRequest 時,伺服器會刷新該緩衝區。如果任何語句遇到錯誤,伺服器會中止當前事務,並且不會執行佇列中的任何後續命令,直到下一個同步點;會為每個這樣的命令產生 PGRES_PIPELINE_ABORTED 結果。(即使管線中的命令會回滾事務,這仍然適用。)查詢處理在同步點之後恢復。

一個操作依賴於先前操作的結果是沒問題的;例如,一個查詢可以定義一個表,而同一管線中的下一個查詢使用該表。類似地,應用程式可以建立一個命名的預處理語句,並使用同一管線中的後續語句執行它。

32.5.1.2. 處理結果 #

要處理管線中一個查詢的結果,應用程式會重複呼叫 PQgetResult 並處理每個結果,直到 PQgetResult 傳回 null。然後可以使用 PQgetResult 再次檢索管線中下一個查詢的結果,並重複該循環。應用程式像往常一樣處理個別語句結果。當管線中所有查詢的結果都已傳回時,PQgetResult 傳回包含狀態值 PGRES_PIPELINE_SYNC 的結果。

客戶端可以選擇延遲結果處理,直到傳送完整的管線,或將其與在管線中傳送更多查詢交錯進行;請參閱第 32.5.1.4 節

PQgetResult 的行為與一般非同步處理相同,除了它可能包含新的 PGresult 類型 PGRES_PIPELINE_SYNCPGRES_PIPELINE_ABORTEDPGRES_PIPELINE_SYNC 對於每個 PQpipelineSyncPQsendPipelineSync,在管線中的對應點,會回報恰好一次。PGRES_PIPELINE_ABORTED 會取代一般查詢結果發出,針對第一個錯誤以及所有後續結果,直到下一個 PGRES_PIPELINE_SYNC;請參閱第 32.5.1.3 節

在處理管線結果時,PQisBusyPQconsumeInput 等的運作方式與一般情況相同。 特別是,如果在管線中間呼叫 PQisBusy,且到目前為止發出的所有查詢結果都已消耗,則會傳回 0。

libpq 不會向應用程式提供有關目前正在處理的查詢的任何資訊(除了 PQgetResult 傳回 null 以指示我們開始傳回下一個查詢的結果)。 應用程式必須追蹤其傳送查詢的順序,以便將它們與對應的結果相關聯。 應用程式通常會使用狀態機或 FIFO 佇列來完成此操作。

32.5.1.3. 錯誤處理 #

從客戶端的角度來看,在 PQresultStatus 傳回 PGRES_FATAL_ERROR 後,該管線會被標記為中止。 PQresultStatus 會為中止管線中每個剩餘的佇列操作回報一個 PGRES_PIPELINE_ABORTED 結果。 PQpipelineSyncPQsendPipelineSync 的結果會回報為 PGRES_PIPELINE_SYNC,以表示中止管線的結束以及正常結果處理的恢復。

客戶端必須在錯誤恢復期間使用 PQgetResult 處理結果。

如果管線使用隱含交易,則已執行的操作會回復,而排隊在失敗操作之後執行的操作則會完全跳過。 如果管線啟動並提交單個顯式交易(即,第一個語句是 BEGIN,最後一個語句是 COMMIT),則行為相同,只是會話在管線結束時仍保持在中止的交易狀態。 如果管線包含多個顯式交易,則在發生錯誤之前提交的所有交易都將保持提交狀態,目前進行中的交易將被中止,並且所有後續操作將被完全跳過,包括後續交易。 如果在中止狀態下使用顯式交易區塊發生管線同步點,除非下一個命令使用 ROLLBACK 將交易置於正常模式,否則下一個管線將立即中止。

注意

客戶端不得假設在傳送 COMMIT 時工作已提交 — 僅當收到相應的結果以確認提交完成時。 由於錯誤是異步到達的,因此如果出現問題,應用程式需要能夠從上次收到的已提交更改重新啟動,並重新傳送之後完成的工作。

32.5.1.4. 交錯結果處理和查詢分派 #

為了避免大型管線上的死鎖,客戶端應圍繞使用作業系統設施(例如 selectpollWaitForMultipleObjectEx 等)的非阻塞事件迴圈構建。

客戶端應用程式通常應維護一個待分派工作佇列和一個已分派但尚未處理其結果的工作佇列。 當 socket 可寫入時,它應該分派更多工作。 當 socket 可讀取時,它應該讀取結果並處理它們,將它們與其相應結果佇列中的下一個條目進行匹配。 根據可用記憶體,應經常從 socket 讀取結果:無需等到管線結束才讀取結果。 管線應該限定於邏輯工作單元,通常(但不一定)每個管線一個交易。 無需在管線之間退出管線模式並重新進入,也無需等待一個管線完成後再傳送下一個管線。

使用 select() 和簡單狀態機來追蹤已傳送和已接收工作的範例位於 PostgreSQL 原始碼發布中的 src/test/modules/libpq_pipeline/libpq_pipeline.c 中。

32.5.2. 與管線模式相關聯的函數 #

PQpipelineStatus #

傳回 libpq 連線的目前管線模式狀態。

PGpipelineStatus PQpipelineStatus(const PGconn *conn);

PQpipelineStatus 可以傳回以下值之一

PQ_PIPELINE_ON

libpq 連線處於管線模式。

PQ_PIPELINE_OFF

libpq 連線處於管線模式。

PQ_PIPELINE_ABORTED

libpq 連線處於管線模式,並且在處理目前的管線時發生錯誤。 當 PQgetResult 傳回類型為 PGRES_PIPELINE_SYNC 的結果時,中止標誌將被清除。

PQenterPipelineMode #

如果連線目前處於閒置狀態或已處於管線模式,則使連線進入管線模式。

int PQenterPipelineMode(PGconn *conn);

成功時傳回 1。 如果連線目前未處於閒置狀態(即,它有結果準備就緒,或者正在等待來自伺服器的更多輸入等),則傳回 0 並且沒有任何影響。 此函數實際上不會向伺服器傳送任何內容,它只會更改 libpq 連線狀態。

PQexitPipelineMode #

如果連線目前處於具有空佇列且沒有未決結果的管線模式,則使連線退出管線模式。

int PQexitPipelineMode(PGconn *conn);

成功時傳回 1。 如果未處於管線模式,則傳回 1 並且不採取任何動作。 如果目前的語句尚未完成處理,或者尚未呼叫 PQgetResult 以收集來自先前傳送的所有查詢的結果,則傳回 0(在這種情況下,使用 PQerrorMessage 取得有關失敗的更多資訊)。

PQpipelineSync #

通過傳送同步訊息並刷新傳送緩衝區,在管線中標記同步點。 這充當隱式交易和錯誤恢復點的分隔符;請參閱第 32.5.1.3 節

int PQpipelineSync(PGconn *conn);

成功時傳回 1。 如果連線未處於管線模式或傳送 同步訊息失敗,則傳回 0。

PQsendPipelineSync #

通過傳送同步訊息而不刷新傳送緩衝區,在管線中標記同步點。 這充當隱式交易和錯誤恢復點的分隔符;請參閱第 32.5.1.3 節

int PQsendPipelineSync(PGconn *conn);

成功時返回 1。如果連線不在管線模式中,或傳送 同步訊息 失敗,則返回 0。請注意,訊息本身不會自動刷新到伺服器;如有必要,請使用 PQflush

PQsendFlushRequest #

傳送請求,要求伺服器刷新其輸出緩衝區。

int PQsendFlushRequest(PGconn *conn);

成功時返回 1。任何失敗時返回 0。

由於呼叫了 PQpipelineSync,或在非管線模式下的任何請求,伺服器會自動刷新其輸出緩衝區;此函數可用於在管線模式下,不建立同步點的情況下,使伺服器刷新其輸出緩衝區。請注意,請求本身不會自動刷新到伺服器;如有必要,請使用 PQflush

32.5.3. 何時使用管線模式 #

與非同步查詢模式非常相似,使用管線模式時沒有明顯的效能開銷。它增加了客戶端應用程式的複雜性,並且需要格外小心以防止客戶端/伺服器死鎖,但管線模式可以提供相當大的效能改進,以換取因長時間保留狀態而增加的記憶體使用量。

當伺服器距離較遠時,即網路延遲(ping 時間)較高,以及快速連續執行許多小型操作時,管線模式最有用。當每個查詢的執行時間是客戶端/伺服器往返時間的許多倍數時,使用管線化命令通常好處較少。在距離 300 毫秒往返時間的伺服器上執行的 100 個語句操作,如果沒有管線化,僅網路延遲就需要 30 秒;使用管線化,等待伺服器結果的時間可能僅為 0.3 秒。

當您的應用程式執行許多無法輕易轉換為集合操作或 COPY 操作的小型 INSERTUPDATEDELETE 操作時,請使用管線化命令。

當客戶端需要來自一個操作的資訊來產生下一個操作時,管線模式沒有用處。在這種情況下,客戶端必須引入同步點,並等待完整的客戶端/伺服器往返才能獲得其需要的結果。但是,通常可以調整客戶端設計以在伺服器端交換所需的資訊。讀取-修改-寫入循環尤其是不錯的選擇;例如

BEGIN;
SELECT x FROM mytable WHERE id = 42 FOR UPDATE;
-- result: x=2
-- client adds 1 to x:
UPDATE mytable SET x = 3 WHERE id = 42;
COMMIT;

可以用更有效的方式完成:

UPDATE mytable SET x = x + 1 WHERE id = 42;

當單個管線包含多個交易時,管線化不太有用且更複雜(請參閱第 32.5.1.3 節)。



[15] 客戶端將阻塞並嘗試向伺服器傳送查詢,但伺服器將阻塞並嘗試將其已處理的查詢結果傳送給客戶端。 僅當客戶端傳送足夠的查詢以填滿其輸出緩衝區和伺服器的接收緩衝區,然後才切換為處理來自伺服器的輸入時,才會發生這種情況,但很難準確預測何時會發生這種情況。

提交更正

如果您在文件中發現任何不正確、與特定功能的經驗不符或需要進一步說明的地方,請使用此表單來報告文件問題。