2025年9月25日: PostgreSQL 18 釋出!
支援的版本:當前 (18) / 17 / 16 / 15 / 14
開發版本:devel

32.5. 管道模式 #

libpq 管道模式允許應用程式在無需讀取先前傳送查詢結果的情況下發送查詢。利用管道模式,客戶端與伺服器之間的等待時間會縮短,因為多個查詢/結果可以在一次網路事務中傳送/接收。

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

管道模式通常會增加客戶端和伺服器的記憶體消耗,儘管透過仔細且積極地管理傳送/接收佇列可以緩解這種情況。無論連線是阻塞模式還是非阻塞模式,這都適用。

libpq 的管道 API 在 PostgreSQL 14 中引入,它是一個客戶端功能,不需要特殊的伺服器支援,並且可以與任何支援 v3 擴充套件查詢協議的伺服器配合使用。有關更多資訊,請參閱 第 54.2.4 節

32.5.1. 使用管道模式 #

要執行管道操作,應用程式必須將連線切換到管道模式,這透過 PQenterPipelineMode 完成。PQpipelineStatus 可用於測試管道模式是否處於活動狀態。在管道模式下,只允許使用擴充套件查詢協議的非同步操作,不允許使用包含多個 SQL 命令的命令字串,也不允許使用 COPY。使用同步命令執行函式,例如 PQfn, PQexec, PQexecParams, PQprepare, PQexecPrepared, PQdescribePrepared, PQdescribePortal, PQclosePrepared, PQclosePortal,是一種錯誤情況。由於 PQsendQuery 使用簡單查詢協議,因此也不允許使用。PQexitPipelineMode 可讓應用程式在所有已分派命令的結果都已處理並且管道結束結果已被消耗後,返回到非管道模式。

注意

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

32.5.1.1. 執行查詢 #

進入管道模式後,應用程式使用 PQsendQueryParams 或其準備語句同胞 PQsendQueryPrepared 來分派請求。這些請求在客戶端排隊,直到使用 PQpipelineSync 建立管道同步點,或呼叫 PQflush 時才會重新整理到伺服器。PQsendPrepare, PQsendDescribePrepared, PQsendDescribePortal, PQsendClosePrepared, 和 PQsendClosePortal 函式在管道模式下也可用。結果處理如下所述。

伺服器按客戶端傳送的順序執行語句並返回結果。伺服器將立即開始執行管道中的命令,而不是等待管道結束。請注意,結果在伺服器端被緩衝;當使用 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 節

在處理管道結果時,PQisBusy, PQconsumeInput 等函式按正常方式執行。特別地,在管道中間呼叫 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. 交錯結果處理和查詢分派 #

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

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

在 PostgreSQL 原始碼分發版中的 src/test/modules/libpq_pipeline/libpq_pipeline.c 中,有一個使用 select() 和簡單的狀態機來跟蹤已傳送和已接收工作的示例。

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 #

透過傳送 sync 訊息並刷新發送緩衝區,在管道中標記一個同步點。這作為隱式事務的分隔符和錯誤恢復點;請參閱 第 32.5.1.3 節

int PQpipelineSync(PGconn *conn);

如果連線不處於管道模式或傳送 sync 訊息失敗,則返回 0。否則返回 1。

PQsendPipelineSync #

透過傳送 sync 訊息而不刷新發送緩衝區,在管道中標記一個同步點。這作為隱式事務的分隔符和錯誤恢復點;請參閱 第 32.5.1.3 節

int PQsendPipelineSync(PGconn *conn);

如果連線不處於管道模式或傳送 sync 訊息失敗,則返回 0。否則返回 1。請注意,訊息本身不會自動重新整理到伺服器;如果需要,請使用 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] 客戶端將阻塞嘗試向伺服器傳送查詢,但伺服器將阻塞嘗試從已處理的查詢向客戶端傳送結果。這僅發生在客戶端傳送了足夠的查詢來填滿其輸出緩衝區和伺服器的接收緩衝區,然後才開始處理來自伺服器的輸入時,但很難精確預測何時會發生這種情況。

提交更正

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