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 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1

32.4. 非同步命令處理 #

在正常、同步應用程式中,PQexec函式足以提交命令。然而,它有一些對某些使用者可能很重要的缺陷:

  • PQexec會等待命令完成。應用程式可能還有其他工作要做(例如維護使用者介面),在這種情況下,它不想在等待響應時被阻塞。

  • 由於在等待結果時客戶端應用程式的執行被掛起,因此應用程式很難決定它想嘗試取消正在進行的命令。(可以從訊號處理程式中執行此操作,但不能以其他方式執行。)

  • PQexec只能返回一個PGresult結構。如果提交的命令字串包含多個SQL命令,則PQexec會丟棄除最後一個PGresult之外的所有命令。

  • PQexec始終收集命令的整個結果,並將其緩衝在一個PGresult中。雖然這簡化了應用程式的錯誤處理邏輯,但對於包含許多行的結果來說,這可能是不切實際的。

不希望這些限制的應用程式可以改用PQexec所基於的底層函式:PQsendQueryPQgetResult。還有PQsendQueryParamsPQsendPreparePQsendQueryPreparedPQsendDescribePreparedPQsendDescribePortalPQsendClosePreparedPQsendClosePortal,它們可以與PQgetResult一起使用,以分別複製PQexecParamsPQpreparePQexecPreparedPQdescribePreparedPQdescribePortalPQclosePreparedPQclosePortal的功能。

PQsendQuery #

在不等待結果的情況下向伺服器提交命令。如果命令已成功排程,則返回1,如果未成功,則返回0(在這種情況下,使用PQerrorMessage獲取有關失敗的更多資訊)。

int PQsendQuery(PGconn *conn, const char *command);

成功呼叫PQsendQuery後,請呼叫一次或多次PQgetResult以獲取結果。PQsendQuery不能在PQgetResult返回null指標(表示命令已完成)之前再次呼叫(在同一連線上)。

在管道模式下,此函式是被禁止的。

PQsendQueryParams #

在不等待結果的情況下向伺服器提交命令和單獨的引數。

int PQsendQueryParams(PGconn *conn,
                      const char *command,
                      int nParams,
                      const Oid *paramTypes,
                      const char * const *paramValues,
                      const int *paramLengths,
                      const int *paramFormats,
                      int resultFormat);

這等同於PQsendQuery,只是查詢引數可以與查詢字串分開指定。該函式的引數處理方式與PQexecParams相同。與PQexecParams一樣,它只允許查詢字串中有一個命令。

PQsendPrepare #

傳送一個請求,用給定的引數建立一個預備語句,而不等待其完成。

int PQsendPrepare(PGconn *conn,
                  const char *stmtName,
                  const char *query,
                  int nParams,
                  const Oid *paramTypes);

這是PQprepare的非同步版本:如果它能夠排程請求,則返回1,否則返回0。成功呼叫後,請呼叫PQgetResult以確定伺服器是否成功建立了預備語句。該函式的引數處理方式與PQprepare相同。

PQsendQueryPrepared #

傳送一個請求,用給定的引數執行一個預備語句,而不等待結果。

int PQsendQueryPrepared(PGconn *conn,
                        const char *stmtName,
                        int nParams,
                        const char * const *paramValues,
                        const int *paramLengths,
                        const int *paramFormats,
                        int resultFormat);

這類似於PQsendQueryParams,但要執行的命令是透過命名一個先前已準備好的語句而不是提供查詢字串來指定的。該函式的引數處理方式與PQexecPrepared相同。

PQsendDescribePrepared #

提交一個請求,以獲取有關指定預備語句的資訊,而不等待其完成。

int PQsendDescribePrepared(PGconn *conn, const char *stmtName);

這是PQdescribePrepared的非同步版本:如果它能夠排程請求,則返回1,否則返回0。成功呼叫後,請呼叫PQgetResult以獲取結果。該函式的引數處理方式與PQdescribePrepared相同。

PQsendDescribePortal #

提交一個請求,以獲取有關指定portal的資訊,而不等待其完成。

int PQsendDescribePortal(PGconn *conn, const char *portalName);

這是PQdescribePortal的非同步版本:如果它能夠排程請求,則返回1,否則返回0。成功呼叫後,請呼叫PQgetResult以獲取結果。該函式的引數處理方式與PQdescribePortal相同。

PQsendClosePrepared #

提交一個請求,以關閉指定的預備語句,而不等待其完成。

int PQsendClosePrepared(PGconn *conn, const char *stmtName);

這是PQclosePrepared的非同步版本:如果它能夠排程請求,則返回1,否則返回0。成功呼叫後,請呼叫PQgetResult以獲取結果。該函式的引數處理方式與PQclosePrepared相同。

PQsendClosePortal #

提交一個請求,以關閉指定的portal,而不等待其完成。

int PQsendClosePortal(PGconn *conn, const char *portalName);

這是PQclosePortal的非同步版本:如果它能夠排程請求,則返回1,否則返回0。成功呼叫後,請呼叫PQgetResult以獲取結果。該函式的引數處理方式與PQclosePortal相同。

PQgetResult #

等待來自先前PQsendQueryPQsendQueryParamsPQsendPreparePQsendQueryPreparedPQsendDescribePreparedPQsendDescribePortalPQsendClosePreparedPQsendClosePortalPQsendPipelineSyncPQpipelineSync呼叫的下一個結果,並返回它。當命令完成且沒有更多結果時,將返回null指標。

PGresult *PQgetResult(PGconn *conn);

PQgetResult必須被重複呼叫,直到它返回null指標,表示命令已完成。(如果呼叫時沒有活動命令,PQgetResult將立即返回null指標。)來自PQgetResult的每個非null結果都應使用前面描述的PGresult訪問器函式進行處理。完成後不要忘記使用PQclear釋放每個結果物件。請注意,PQgetResult僅在命令處於活動狀態且PQconsumeInput尚未讀取必要響應資料時才會阻塞。

在管道模式下,PQgetResult將正常返回,除非發生錯誤;對於在導致錯誤的查詢之後傳送的任何後續查詢(直到下一個同步點,但不包括它),將返回一種特殊的PGRES_PIPELINE_ABORTED型別的結果,然後返回null指標。當達到管道同步點時,將返回PGRES_PIPELINE_SYNC型別的結果。同步點之後的下一個查詢的結果會立即跟上(也就是說,同步點之後不會返回null指標)。

注意

即使PQresultStatus指示發生致命錯誤,也應呼叫PQgetResult直到它返回null指標,以允許libpq完全處理錯誤資訊。

使用PQsendQueryPQgetResult解決了PQexec的一個問題:如果命令字串包含多個SQL命令,則可以單獨獲取這些命令的結果。(順便說一句,這允許了一種簡單的重疊處理形式:客戶端可以在伺服器仍在處理同一命令字串中的後續查詢時,處理一個命令的結果。)

使用PQsendQueryPQgetResult還可以實現另一個經常需要的特性,即一次檢索大量查詢結果(每次檢索有限行)。這在第 32.6 節中討論。

僅透過呼叫PQgetResult,仍然會導致客戶端阻塞,直到伺服器完成下一個SQL命令。這可以透過正確使用另外兩個函式來避免

PQconsumeInput #

如果有來自伺服器的輸入,則消耗它。

int PQconsumeInput(PGconn *conn);

PQconsumeInput通常返回1,表示沒有錯誤,但如果出現任何問題,則返回0(在這種情況下,可以諮詢PQerrorMessage)。請注意,結果並不表示是否實際收集了任何輸入資料。呼叫PQconsumeInput後,應用程式可以檢查PQisBusy和/或PQnotifies以檢視其狀態是否已更改。

PQconsumeInput即使應用程式尚未準備好處理結果或通知,也可以被呼叫。該函式將讀取可用資料並將其儲存在緩衝區中,從而使select()就緒指示消失。因此,應用程式可以使用PQconsumeInput立即清除select()條件,然後悠閒地檢查結果。

PQisBusy #

如果命令正在忙(即PQgetResult會阻塞等待輸入),則返回1。返回0表示可以呼叫PQgetResult而不會阻塞。

int PQisBusy(PGconn *conn);

PQisBusy本身不會嘗試從伺服器讀取資料;因此,必須先呼叫PQconsumeInput,否則忙狀態將永遠不會結束。

使用這些函式的典型應用程式將有一個主迴圈,該迴圈使用select()poll()來等待它必須響應的所有條件。其中一個條件是來自伺服器的可用輸入,在select()方面,這意味著在由PQsocket標識的檔案描述符上有可讀資料。當主迴圈檢測到輸入已準備好時,它應該呼叫PQconsumeInput來讀取輸入。然後,它可以呼叫PQisBusy,然後呼叫PQgetResult(如果PQisBusy返回false(0))。它還可以呼叫PQnotifies來檢測NOTIFY訊息(請參閱第 32.9 節)。

使用PQsendQuery/PQgetResult的客戶端還可以嘗試取消伺服器仍在處理的命令;請參閱第 32.7 節。但是,無論PQcancelBlocking的返回值如何,應用程式都必須繼續進行PQgetResult的正常結果讀取序列。成功取消只會使命令比預期的更早終止。

透過使用上述函式,可以避免在等待資料庫伺服器輸入時阻塞。但是,應用程式仍有可能在等待向伺服器傳送輸出時阻塞。這相對不常見,但如果傳送非常長的SQL命令或資料值,則可能會發生。(但是,如果應用程式透過COPY IN傳送資料,則可能性要大得多。)為了防止這種可能性並實現完全非阻塞的資料庫操作,可以使用以下附加函式。

PQsetnonblocking #

設定連線的非阻塞狀態。

int PQsetnonblocking(PGconn *conn, int arg);

如果arg為1,則將連線狀態設定為非阻塞;如果arg為0,則設定為阻塞。返回0表示成功,-1表示錯誤。

在非阻塞狀態下,成功的PQsendQueryPQputlinePQputnbytesPQputCopyDataPQendcopy呼叫將不會阻塞;它們的更改將儲存在本地輸出緩衝區中,直到重新整理為止。不成功的呼叫將返回錯誤,並且必須重試。

請注意,PQexec不遵守非阻塞模式;如果呼叫它,它仍然會以阻塞方式執行。

PQisnonblocking #

返回資料庫連線的阻塞狀態。

int PQisnonblocking(const PGconn *conn);

如果連線設定為非阻塞模式,則返回1;如果為阻塞模式,則返回0。

PQflush #

嘗試將任何排隊的輸出資料重新整理到伺服器。如果成功(或傳送佇列為空),則返回0;如果由於某種原因失敗,則返回-1;如果仍無法傳送傳送佇列中的所有資料,則返回1(這種情況僅在連線為非阻塞時發生)。

int PQflush(PGconn *conn);

在非阻塞連線上傳送任何命令或資料後,請呼叫PQflush。如果它返回1,請等待套接字變得可讀或可寫。如果它變得可寫,請再次呼叫PQflush。如果它變得可讀,請呼叫PQconsumeInput,然後再次呼叫PQflush。重複此過程,直到PQflush返回0。(必須檢查可讀性並使用PQconsumeInput清空輸入,因為伺服器可能會阻塞嘗試向我們傳送資料(例如,NOTICE訊息),並且在您讀取其資料之前不會讀取我們的資料。)一旦PQflush返回0,請等待套接字可讀,然後如上所述讀取響應。

提交更正

如果您在文件中看到任何不正確的內容,與您在使用特定功能時的經驗不符,或者需要進一步澄清,請使用此表單報告文件問題。