本節描述訊息流和每種訊息型別的語義。(每種訊息的確切表示細節出現在 第 54.7 節。) 根據連線的狀態,有幾種不同的子協議:啟動、查詢、函式呼叫、COPY
和終止。對於非同步操作(包括通知響應和命令取消),也有特殊規定,這些操作可以在啟動階段之後隨時發生。
要開始一個會話,前端會開啟與伺服器的連線併發送一個啟動訊息。此訊息包括使用者名稱稱以及使用者想要連線的資料庫名稱;它還標識了要使用的特定協議版本。(可選地,啟動訊息可以包含執行時引數的額外設定。)然後,伺服器將使用此資訊及其配置檔案(如 pg_hba.conf
)的內容來確定連線是否初步可接受,以及需要(如果需要)哪些額外的認證。
然後,伺服器傳送一個適當的認證請求訊息,前端必須用一個適當的認證響應訊息(例如密碼)來回復。對於所有除了 GSSAPI、SSPI 和 SASL 之外的認證方法,最多有一個請求和一個響應。在某些方法中,前端根本不需要響應,因此不會發生認證請求。對於 GSSAPI、SSPI 和 SASL,可能需要多次資料包交換來完成認證。
認證週期以伺服器拒絕連線嘗試(ErrorResponse)或傳送 AuthenticationOk 結束。
在此階段,伺服器可能傳送的訊息是
連線嘗試已被拒絕。然後伺服器立即關閉連線。
認證交換成功完成。
前端現在必須與伺服器進行 Kerberos V5 認證對話(此處未描述,屬於 Kerberos 規範的一部分)。如果成功,伺服器將響應 AuthenticationOk,否則響應 ErrorResponse。此功能已不再支援。
前端現在必須傳送一個 PasswordMessage,其中包含明文密碼。如果密碼正確,伺服器將響應 AuthenticationOk,否則響應 ErrorResponse。
前端現在必須傳送一個 PasswordMessage,其中包含透過 MD5 加密的密碼(連同使用者名稱),然後再使用 AuthenticationMD5Password 訊息中指定的 4 位元組隨機鹽再次加密。如果密碼正確,伺服器將響應 AuthenticationOk,否則響應 ErrorResponse。實際的 PasswordMessage 可以用 SQL 計算為 concat('md5', md5(concat(md5(concat(password, username)), random-salt)))
。(請記住 md5()
函式返回其結果為十六進位制字串。)
MD5 加密密碼的支援已棄用,將在 PostgreSQL 的未來版本中移除。有關遷移到其他密碼型別的詳細資訊,請參閱第 20.5 節。
前端現在必須發起 GSSAPI 協商。前端將傳送一個 GSSResponse 訊息,其中包含 GSSAPI 資料流的第一部分作為響應。如果需要進一步的訊息,伺服器將響應 AuthenticationGSSContinue。
前端現在必須發起 SSPI 協商。前端將傳送一個 GSSResponse,其中包含 SSPI 資料流的第一部分作為響應。如果需要進一步的訊息,伺服器將響應 AuthenticationGSSContinue。
此訊息包含來自 GSSAPI 或 SSPI 協商上一步(AuthenticationGSS、AuthenticationSSPI 或之前的 AuthenticationGSSContinue)的響應資料。如果此訊息中的 GSSAPI 或 SSPI 資料指示需要更多資料來完成認證,前端必須將這些資料作為另一個 GSSResponse 訊息傳送。如果此訊息完成了 GSSAPI 或 SSPI 認證,伺服器將接下來發送 AuthenticationOk 表示成功認證,或 ErrorResponse 表示失敗。
前端現在必須使用訊息中列出的 SASL 機制之一發起 SASL 協商。前端將傳送一個 SASLInitialResponse,其中包含所選機制的名稱以及 SASL 資料流的第一部分作為響應。如果需要進一步的訊息,伺服器將響應 AuthenticationSASLContinue。有關詳細資訊,請參見 第 54.3 節。
此訊息包含來自 SASL 協商上一步(AuthenticationSASL 或之前的 AuthenticationSASLContinue)的挑戰資料。前端必須用 SASLResponse 訊息進行響應。
SASL 認證已完成,並附帶了機制特定的附加資料供客戶端使用。伺服器將接下來發送 AuthenticationOk 表示成功認證,或 ErrorResponse 表示失敗。此訊息僅在 SASL 機制指定了在完成時從伺服器傳送到客戶端的附加資料時傳送。
伺服器不支援客戶端請求的小協議版本,但支援協議的早期版本;此訊息指示最高支援的小版本。如果客戶端在啟動包中請求了不支援的協議選項(即以 _pq_.
開頭),也將傳送此訊息。
在此訊息之後,認證將使用伺服器指示的版本繼續。如果客戶端不支援較早的版本,它應立即關閉連線。如果伺服器不傳送此訊息,則表示它支援客戶端請求的協議版本和所有協議選項。
如果前端不支援伺服器請求的認證方法,則應立即關閉連線。
在收到 AuthenticationOk 後,前端必須等待伺服器的進一步訊息。在此階段,後端程序正在啟動,前端只是一個旁觀者。啟動嘗試仍有可能失敗(ErrorResponse),或者伺服器可能拒絕支援所請求的小協議版本(NegotiateProtocolVersion),但在正常情況下,後端將傳送一些 ParameterStatus 訊息、BackendKeyData,最後是 ReadyForQuery。
在此階段,後端將嘗試應用啟動訊息中提供的任何其他執行時引數設定。如果成功,這些值將成為會話預設值。錯誤將導致 ErrorResponse 和退出。
在此階段,後端可能傳送的訊息是
此訊息提供了一個前端必須儲存的秘密金鑰資料,以便以後能夠發出取消請求。前端不應響應此訊息,而應繼續等待 ReadyForQuery 訊息。
PostgreSQL 伺服器將始終傳送此訊息,但一些不支援查詢取消的第三方後端協議實現則已知不會發送。
此訊息告知前端有關後端引數的當前(初始)設定,例如 client_encoding 或 DateStyle。前端可以忽略此訊息,或記錄設定以備將來使用;有關更多詳細資訊,請參見 第 54.2.7 節。前端不應響應此訊息,而應繼續等待 ReadyForQuery 訊息。
啟動已完成。前端現在可以發出命令。
啟動失敗。傳送此訊息後連線將關閉。
已發出警告訊息。前端應顯示訊息,但繼續等待 ReadyForQuery 或 ErrorResponse。
ReadyForQuery 訊息與後端在每個命令週期後會傳送的訊息相同。根據前端的編碼需求,可以合理地將 ReadyForQuery 視為啟動一個命令週期,或者將 ReadyForQuery 視為結束啟動階段和每個後續命令週期。
簡單查詢週期由前端傳送 Query 訊息到後端啟動。該訊息包含一個 SQL 命令(或多個命令),以文字字串表示。然後,後端會根據查詢命令字串的內容傳送一個或多個響應訊息,最後傳送一個 ReadyForQuery 響應訊息。ReadyForQuery 告知前端,它可以安全地傳送新命令。(前端實際上不必等待 ReadyForQuery 再發出另一個命令,但這樣前端必須自行負責弄清楚如果早期命令失敗且已發出的後續命令成功了會發生什麼。)
後端可能傳送的響應訊息是
SQL 命令正常完成。
後端已準備好將資料從前端複製到表中;請參見 第 54.2.6 節。
後端已準備好將資料從表中複製到前端;請參見 第 54.2.6 節。
指示將按預期返回行作為對 SELECT
、FETCH
等查詢的響應。此訊息的內容描述了行的列布局。隨後會為返回給前端的每一行傳送 DataRow 訊息。
由 SELECT
、FETCH
等查詢返回的行集中的一行。
已識別出空查詢字串。
發生了錯誤。
查詢字串的處理已完成。將傳送一個單獨的訊息來指示這一點,因為查詢字串可能包含多個 SQL 命令。(CommandComplete 標記一個 SQL 命令的處理結束,而不是整個字串。)無論處理是成功終止還是出錯終止,都會發送 ReadyForQuery。
已發出與查詢相關的警告訊息。Notice 是其他響應的附加資訊,即後端將繼續處理命令。
對 SELECT
查詢(或其他返回行集的查詢,如 EXPLAIN
或 SHOW
)的響應通常由 RowDescription、零個或多個 DataRow 訊息,然後是 CommandComplete 組成。到前端或從前端的 COPY
操作會呼叫特殊協議,如 第 54.2.6 節 所述。所有其他查詢型別通常只生成一個 CommandComplete 訊息。
由於查詢字串可能包含多個查詢(以分號分隔),因此在後端完成處理查詢字串之前,可能會有多個此類響應序列。當整個字串處理完畢且後端已準備好接受新查詢字串時,將發出 ReadyForQuery。
如果收到一個完全為空(除空格外無內容)的查詢字串,響應將是 EmptyQueryResponse 後跟 ReadyForQuery。
發生錯誤時,將發出 ErrorResponse,然後是 ReadyForQuery。ErrorResponse 將中止查詢字串的所有進一步處理(即使其中還有剩餘的查詢)。請注意,這可能發生在單個查詢生成的響應訊息序列的中間。
在簡單查詢模式下,檢索到的值的格式始終是文字,除非給定的命令是帶有 BINARY
選項宣告的游標的 FETCH
。在這種情況下,檢索到的值是二進位制格式。RowDescription 訊息中給出的格式程式碼指示了正在使用的格式。
前端必須準備好在期望接收任何其他型別的訊息時接受 ErrorResponse 和 NoticeResponse 訊息。另請參見 第 54.2.7 節,其中介紹了後端可能由於外部事件而生成的訊息。
推薦的做法是採用狀態機風格編寫前端,以在任何可能合理的時機接受任何訊息型別,而不是硬編碼對訊息確切序列的假設。
當一個簡單 Query 訊息包含多個 SQL 語句(以分號分隔)時,這些語句將作為單個事務執行,除非包含顯式的事務控制命令來強制不同的行為。例如,如果訊息包含
INSERT INTO mytable VALUES(1); SELECT 1/0; INSERT INTO mytable VALUES(2);
那麼 SELECT
中的除零錯誤將強制回滾第一個 INSERT
。此外,由於訊息的執行在第一個錯誤處被中止,第二個 INSERT
根本不會被嘗試。
如果訊息包含
BEGIN; INSERT INTO mytable VALUES(1); COMMIT; INSERT INTO mytable VALUES(2); SELECT 1/0;
那麼第一個 INSERT
將由顯式的 COMMIT
命令提交。第二個 INSERT
和 SELECT
仍被視為單個事務,因此除零錯誤將回滾第二個 INSERT
,但不是第一個。
此行為是透過在 隱式事務塊 中執行多語句 Query 訊息中的語句來實現的,除非有某個顯式事務塊供它們執行。隱式事務塊與常規事務塊的主要區別在於,隱式塊會在 Query 訊息結束時自動關閉,要麼透過隱式提交(如果沒有錯誤),要麼透過隱式回滾(如果有錯誤)。這與單個語句執行時(不在事務塊中)發生的隱式提交或回滾類似。
如果會話已在事務塊中(由於之前訊息中的 BEGIN
),則 Query 訊息將繼續該事務塊,無論訊息是包含一個語句還是多個語句。但是,如果 Query 訊息包含一個 COMMIT
或 ROLLBACK
來關閉現有事務塊,那麼任何後續語句都將在隱式事務塊中執行。反之,如果在多語句 Query 訊息中出現 BEGIN
,則它將啟動一個常規事務塊,該事務塊將僅由顯式的 COMMIT
或 ROLLBACK
終止,無論是在此 Query 訊息中還是在後續訊息中出現。如果 BEGIN
出現在作為隱式事務塊執行的某些語句之後,則這些語句不會立即提交;實際上,它們將被追溯地包含在新常規事務塊中。
出現在隱式事務塊中的 COMMIT
或 ROLLBACK
將正常執行,關閉隱式塊;但是,會發出警告,因為沒有 BEGIN
的 COMMIT
或 ROLLBACK
可能表示一個錯誤。如果後面還有語句,將為它們啟動一個新的隱式事務塊。
隱式事務塊不允許使用儲存點,因為它們會與在任何錯誤時自動關閉塊的行為衝突。
請記住,無論可能存在什麼事務控制命令,Query 訊息的執行將在第一個錯誤處停止。因此,例如給定
BEGIN; SELECT 1/0; ROLLBACK;
在一個簡單的 Query 訊息中,會話將停留在失敗的常規事務塊中,因為除零錯誤後未到達 ROLLBACK
。還需要另一個 ROLLBACK
才能將會話恢復到可用狀態。
另一個值得注意的行為是,在執行任何語句之前,會對整個查詢字串進行初始的詞法和語法分析。因此,後期語句中的簡單錯誤(如拼寫錯誤的關鍵字)可能會阻止任何語句的執行。這通常對使用者是不可見的,因為作為隱式事務塊執行的所有語句都會回滾。但是,在嘗試在多語句 Query 中執行多個事務時,可能會顯示出來。例如,如果一個拼寫錯誤將我們之前的示例變成了
BEGIN; INSERT INTO mytable VALUES(1); COMMIT; INSERT INTO mytable VALUES(2); SELCT 1/0;
那麼將不會執行任何語句,導致可見的差異是第一個 INSERT
未被提交。在語義分析或之後檢測到的錯誤,如拼寫錯誤的表或列名,則不會產生此效果。
最後,請注意,Query 訊息中的所有語句都將觀察到相同的 statement_timestamp()
值,因為該時間戳僅在收到 Query 訊息時更新。除了查詢字串結束先前開始的事務並開始新事務的情況外,這將導致它們都觀察到相同的 transaction_timestamp()
值。
擴充套件查詢協議將上述簡單查詢協議分解為多個步驟。準備階段的結果可以重用多次,以提高效率。此外,還有其他功能可用,例如可以將資料值作為單獨的引數提供,而無需直接插入查詢字串。
在擴充套件協議中,前端首先發送一個 Parse 訊息,其中包含文字查詢字串,可選地包含一些關於引數佔位符資料型別的資訊,以及目標預備語句物件的名稱(空字串選擇未命名預備語句)。響應是 ParseComplete 或 ErrorResponse。引數資料型別可以由 OID 指定;如果未給出,解析器將嘗試以與無型別文字字串常量相同的方式推斷資料型別。
引數資料型別可以透過將其設定為零來省略,或者透過使引數型別 OID 陣列比查詢字串中使用的引數符號($
n
)的數量短來省略。另一個特殊情況是,引數的型別可以指定為 void
(即 void
偽型別的 OID)。這旨在允許引數符號用於實際上是 OUT 引數的函式引數。通常沒有可以用 void
引數的上下文,但如果這樣的引數符號出現在函式的引數列表中,它將被有效地忽略。例如,一個像 foo($1,$2,$3,$4)
這樣的函式呼叫,如果 $3
和 $4
被指定為 void
型別,則可以匹配一個有兩個 IN 和兩個 OUT 引數的函式。
Parse 訊息中包含的查詢字串不能包含多個 SQL 語句;否則會報告語法錯誤。簡單查詢協議中不存在此限制,但在擴充套件協議中存在,因為允許預備語句或遊標包含多個命令會使協議過於複雜。
如果成功建立,命名預備語句物件將一直存在直到當前會話結束,除非被顯式銷燬。未命名的預備語句僅在發出指定未命名語句為目標的下一個 Parse 語句之前有效。(請注意,簡單的 Query 訊息也會銷燬未命名語句。)命名預備語句必須在被另一個 Parse 訊息重新定義之前顯式關閉,但對於未命名語句則不需要。命名預備語句也可以在 SQL 命令級別建立和訪問,使用 PREPARE
和 EXECUTE
。
一旦存在預備語句,就可以使用 Bind 訊息將其準備好執行。Bind 訊息指定源預備語句的名稱(空字串表示未命名預備語句)、目標遊標的名稱(空字串表示未命名遊標),以及用於預備語句中的任何引數佔位符的值。提供的引數集必須與預備語句所需的匹配。(如果在 Parse 訊息中聲明瞭任何 void
引數,則在 Bind 訊息中為它們傳遞 NULL 值。)Bind 還指定了查詢返回的任何資料使用的格式;格式可以整體指定,也可以按列指定。響應是 BindComplete 或 ErrorResponse。
文字和二進位制輸出之間的選擇由 Bind 中給出的格式程式碼決定,與所涉及的 SQL 命令無關。使用擴充套件查詢協議時,遊標宣告中的 BINARY
屬性無關緊要。
查詢規劃通常發生在處理 Bind 訊息時。如果預備語句沒有引數,或者被反覆執行,伺服器可能會儲存建立的計劃並在後續對同一預備語句的 Bind 訊息期間重用它。然而,只有當它發現可以建立一個通用計劃,並且該計劃的效率與依賴於提供的特定引數值的計劃相比沒有太大的劣勢時,才會這樣做。這在協議的意義上是透明發生的。
如果成功建立,命名遊標物件將一直存在直到當前事務結束,除非被顯式銷燬。未命名的遊標將在事務結束時銷燬,或在發出指定未命名遊標為目標的下一個 Bind 語句後立即銷燬。(請注意,簡單的 Query 訊息也會銷燬未命名遊標。)命名遊標必須在被另一個 Bind 訊息重新定義之前顯式關閉,但對於未命名遊標則不需要。命名遊標也可以在 SQL 命令級別建立和訪問,使用 DECLARE CURSOR
和 FETCH
。
一旦存在遊標,就可以使用 Execute 訊息來執行它。Execute 訊息指定遊標名稱(空字串表示未命名遊標)以及最大結果行數(零表示“fetch all rows”)。結果行數僅對於包含返回行集的命令的遊標才有意義;在其他情況下,命令總是執行到完成,並且行數被忽略。Execute 的可能響應與上面描述的簡單查詢協議發出的查詢響應相同,不同之處在於 Execute 不會發出 ReadyForQuery 或 RowDescription。
如果 Execute 在完成遊標執行之前終止(由於達到非零結果行數),它將傳送 PortalSuspended 訊息;此訊息的出現告訴前端應針對同一遊標發出另一個 Execute 來完成操作。直到遊標執行完成,才會傳送指示源 SQL 命令完成的 CommandComplete 訊息。因此,Execute 階段總是以收到以下訊息之一而終止:CommandComplete、EmptyQueryResponse(如果遊標是由空查詢字串建立的)、ErrorResponse 或 PortalSuspended。
在完成每一系列擴充套件查詢訊息後,前端應發出 Sync 訊息。這個無引數的訊息會導致後端在不在 BEGIN
/COMMIT
事務塊中的情況下關閉當前事務(“關閉”意味著如果沒有錯誤則提交,如果有錯誤則回滾)。然後發出 ReadyForQuery 響應。Sync 的目的是為錯誤恢復提供一個重新同步點。當在處理任何擴充套件查詢訊息時檢測到錯誤,後端將發出 ErrorResponse,然後讀取並丟棄訊息直到遇到 Sync,然後發出 ReadyForQuery 並返回正常的訊息處理。(但請注意,如果在處理 Sync 時檢測到錯誤,則不會發生跳過——這確保了每個 Sync 只發送一個 ReadyForQuery。)
Sync 不會導致用 BEGIN
開啟的事務塊被關閉。檢測到這種情況是可能的,因為 ReadyForQuery 訊息包含事務狀態資訊。
除了這些基本、必需的操作之外,還有幾個可選操作可以與擴充套件查詢協議一起使用。
Describe 訊息(遊標變體)指定一個現有遊標的名稱(或未命名遊標的空字串)。響應是 RowDescription 訊息,描述執行遊標將返回的行;或者在遊標不包含返回行的查詢時顯示 NoData 訊息;或者在沒有此類遊標時顯示 ErrorResponse。
Describe 訊息(語句變體)指定一個現有預備語句的名稱(或未命名預備語句的空字串)。響應是 ParameterDescription 訊息,描述語句所需的引數,後跟 RowDescription 訊息,描述語句最終執行時將返回的行(或者在語句不返回行時顯示 NoData 訊息)。如果不存在這樣的預備語句,將發出 ErrorResponse。請注意,由於尚未發出 Bind,返回列的格式尚未被後端知曉;在這種情況下,RowDescription 訊息中的格式程式碼欄位將為零。
在大多數情況下,前端應在發出 Execute 之前發出 Describe 的一個變體,以確保它知道如何解釋它將收到的結果。
Close 訊息關閉現有的預備語句或遊標並釋放資源。對不存在的語句或遊標名稱發出 Close 不算錯誤。響應通常是 CloseComplete,但如果在釋放資源時遇到任何困難,則可能是 ErrorResponse。請注意,關閉預備語句會隱式關閉由該語句構建的任何開啟的遊標。
Flush 訊息不產生任何特定輸出,但強制後端傳遞其輸出緩衝區中任何待處理的資料。如果前端希望在發出更多命令之前檢查該命令的結果,則必須在 Sync 之外的任何擴充套件查詢命令之後傳送 Flush。沒有 Flush,後端返回的訊息將被合併成最少數量的資料包,以最大限度地減少網路開銷。
簡單的 Query 訊息大致相當於 Parse、Bind、遊標 Describe、Execute、Close、Sync 系列,使用未命名的預備語句和遊標物件,並且不帶引數。一個區別是它將接受查詢字串中的多個 SQL 語句,並自動連續執行每個語句的繫結/描述/執行序列。另一個區別是它不會返回 ParseComplete、BindComplete、CloseComplete 或 NoData 訊息。
使用擴充套件查詢協議允許 流水線,這意味著傳送一系列查詢而不等待前一個完成。這減少了完成給定操作系列所需的網路往返次數。但是,如果其中一個步驟失敗,使用者必須仔細考慮所需的行為,因為後續查詢已經在傳送到伺服器的途中。
處理這種情況的一種方法是將整個查詢系列作為一個事務,即將其包裝在 BEGIN
... COMMIT
中。但是,如果希望其中一些命令獨立於其他命令提交,則此方法無濟於事。
擴充套件查詢協議提供了另一種管理此問題的方法,即在步驟依賴時省略傳送 Sync 訊息。由於在發生錯誤後,後端將跳過命令訊息直到找到 Sync,這允許流水線中的後續命令在前面的命令失敗時自動跳過,而無需客戶端顯式地用 BEGIN
和 COMMIT
來管理。可以由 Sync 訊息分隔獨立提交的流水線段。
如果客戶端沒有發出顯式的 BEGIN
,則會啟動一個隱式事務塊,並且每個 Sync 通常會導致在先前步驟成功時進行隱式 COMMIT
,或者在它們失敗時進行隱式 ROLLBACK
。伺服器在第一個命令在沒有 sync 的情況下結束時才會檢測到這個隱式事務塊。有幾個 DDL 命令(如 CREATE DATABASE
)不能在事務塊中執行。如果在流水線中執行其中一個,除非它是 Sync 之後的第一個命令,否則將失敗。此外,成功後它將強制立即提交以保持資料庫一致性。因此,在此類命令之後立即發出 Sync 除了響應 ReadyForQuery 外沒有其他作用。
使用此方法時,流水線的完成必須透過計算 ReadyForQuery 訊息並等待其達到傳送的 Sync 數量來確定。計算命令完成響應是不可靠的,因為其中一些命令可能會被跳過,因此不會產生完成訊息。
函式呼叫子協議允許客戶端請求直接呼叫資料庫 pg_proc
系統目錄中存在的任何函式。客戶端必須具有執行函式的許可權。
函式呼叫子協議是一個遺留功能,在新程式碼中最好避免使用。透過設定一個執行 SELECT function($1, ...)
的預備語句,可以完成類似的結果。然後,函式呼叫週期可以被 Bind/Execute 替換。
函式呼叫週期由前端傳送 FunctionCall 訊息到後端啟動。然後,後端會根據函式呼叫的結果傳送一個或多個響應訊息,最後傳送一個 ReadyForQuery 響應訊息。ReadyForQuery 告知前端,它可以安全地傳送新查詢或函式呼叫。
後端可能傳送的響應訊息是
發生了錯誤。
函式呼叫已完成,並返回訊息中給出的結果。(請注意,函式呼叫協議只能處理單個標量結果,不能處理行型別或結果集。)
函式呼叫處理已完成。無論處理是成功終止還是出錯終止,都會發送 ReadyForQuery。
已發出與函式呼叫相關的警告訊息。Notice 是其他響應的附加資訊,即後端將繼續處理命令。
COPY
命令允許與伺服器之間進行高速批次資料傳輸。複製輸入(copy-in)和複製輸出(copy-out)操作都會將連線切換到一種特定的子協議,直到操作完成。
複製輸入模式(資料傳輸到伺服器)在後端執行 COPY FROM STDIN
SQL 語句時啟動。後端向前端傳送一個 CopyInResponse 訊息。前端隨後應傳送零個或多個 CopyData 訊息,形成一個輸入資料流。(訊息邊界不必與行邊界嚴格對應,儘管這通常是一個合理的選擇。)前端可以透過傳送 CopyDone 訊息(表示成功完成)或 CopyFail 訊息(這將導致 COPY
SQL 語句失敗並報錯)來終止複製輸入模式。然後,後端會恢復到 COPY
命令開始之前的連線處理模式,該模式要麼是簡單查詢協議,要麼是擴充套件查詢協議。接下來,後端會發送 CommandComplete(如果成功)或 ErrorResponse(如果不成功)。
如果在複製輸入模式期間發生後端檢測到的錯誤(包括收到 CopyFail 訊息),後端將發出一個 ErrorResponse 訊息。如果 COPY
命令是透過擴充套件查詢訊息發出的,後端將丟棄前端訊息,直到收到 Sync 訊息,然後它會發出 ReadyForQuery 並返回正常處理。如果 COPY
命令是在簡單查詢訊息中發出的,該訊息的其餘部分將被丟棄,然後發出 ReadyForQuery。無論哪種情況,前端隨後發出的任何 CopyData、CopyDone 或 CopyFail 訊息都將被忽略。
後端在複製輸入模式期間會忽略收到的 Flush 和 Sync 訊息。收到任何其他非複製訊息型別都將構成一個錯誤,該錯誤將按上述方式中止複製輸入狀態。(對 Flush 和 Sync 的例外是為了方便客戶端庫,這些庫總是在傳送 Execute 訊息後傳送 Flush 或 Sync,而不檢查要執行的命令是否是 COPY FROM STDIN
。)
複製輸出模式(資料從伺服器傳輸)在後端執行 COPY TO STDOUT
SQL 語句時啟動。後端向前端傳送一個 CopyOutResponse 訊息,隨後傳送零個或多個 CopyData 訊息(每個行一個),最後傳送 CopyDone。然後,後端恢復到 COPY
命令開始之前的連線處理模式,併發送 CommandComplete。前端無法中止傳輸(除非關閉連線或發出取消請求),但可以丟棄不需要的 CopyData 和 CopyDone 訊息。
如果在複製輸出模式期間發生後端檢測到的錯誤,後端將發出一個 ErrorResponse 訊息並恢復到正常處理。前端應將收到 ErrorResponse 視為終止複製輸出模式。
NoticeResponse 和 ParameterStatus 訊息可能插入在 CopyData 訊息之間;前端必須處理這些情況,並且還應準備好處理其他非同步訊息型別(請參閱 第 54.2.7 節)。否則,除 CopyData 或 CopyDone 之外的任何訊息型別都可能被視為終止複製輸出模式。
還有一種與複製相關的模式稱為 copy-both,它允許與伺服器進行雙向(以及)高速批次資料傳輸。Copy-both 模式在處於 walsender 模式的後端執行 START_REPLICATION
語句時啟動。後端向前端傳送一個 CopyBothResponse 訊息。然後,後端和前端都可以傳送 CopyData 訊息,直到一方傳送 CopyDone 訊息。在客戶端傳送 CopyDone 訊息之後,連線將從 copy-both 模式切換到 copy-out 模式,並且客戶端不能再發送 CopyData 訊息。同樣,當伺服器傳送 CopyDone 訊息時,連線將進入 copy-in 模式,並且伺服器不能再發送 CopyData 訊息。在雙方都發送了 CopyDone 訊息之後,複製模式將被終止,後端恢復到命令處理模式。如果在 copy-both 模式期間發生後端檢測到的錯誤,後端將發出一個 ErrorResponse 訊息,丟棄前端訊息直到收到 Sync 訊息,然後發出 ReadyForQuery 並返回正常處理。前端應將收到 ErrorResponse 視為終止雙向複製;在這種情況下不應傳送 CopyDone。有關透過 copy-both 模式傳輸的子協議的更多資訊,請參閱 第 54.4 節。
CopyInResponse、CopyOutResponse 和 CopyBothResponse 訊息包含通知前端每行列數以及每列使用的格式程式碼的欄位。(在當前實現中,給定 COPY
操作的所有列將使用相同的格式,但訊息設計並未假定這一點。)
在後端傳送不完全由前端命令流直接觸發的訊息的幾種情況下。前端必須隨時準備處理這些訊息,即使在未執行查詢時也是如此。至少,在開始讀取查詢響應之前,應該檢查這些情況。
外部活動可能會生成 NoticeResponse 訊息;例如,如果資料庫管理員發出“快速”資料庫關閉命令,後端將在關閉連線之前傳送一個 NoticeResponse 來指示此事實。因此,即使連線名義上處於空閒狀態,前端也應始終準備好接收和顯示 NoticeResponse 訊息。
每當後端認為前端應該瞭解的任何引數的活動值發生變化時,都會生成 ParameterStatus 訊息。最常見的情況是響應前端執行的 SET
SQL 命令,這種情況實際上是同步的——但引數狀態更改也可能因為管理員更改了配置檔案然後向伺服器傳送了 SIGHUP 訊號而發生。此外,如果 SET
命令被回滾,將生成一個適當的 ParameterStatus 訊息來報告當前有效值。
目前,有一個硬編碼的引數集,將為這些引數生成 ParameterStatus。它們是:
application_name |
scram_iterations |
client_encoding |
search_path |
DateStyle |
server_encoding |
default_transaction_read_only |
server_version |
in_hot_standby |
session_authorization |
integer_datetimes |
standard_conforming_strings |
IntervalStyle |
TimeZone |
is_superuser |
(default_transaction_read_only
和 in_hot_standby
在 14 版之前的版本中未報告;scram_iterations
在 16 版之前的版本中未報告;search_path
在 18 版之前的版本中未報告。)請注意,server_version
、server_encoding
和 integer_datetimes
是偽引數,在啟動後無法更改。此集合將來可能會更改,甚至可能變得可配置。因此,前端應簡單地忽略它不理解或不關心的引數的 ParameterStatus。
如果前端發出 LISTEN
命令,那麼每當為同一頻道名稱執行 NOTIFY
命令時,後端都會發送一個 NotificationResponse 訊息(不要與 NoticeResponse 混淆!)。
目前,NotificationResponse 只能在事務之外發送,因此它不會出現在命令-響應系列中間,儘管它可能在 ReadyForQuery 之前發生。然而,設計假設這一點是不明智的。好的做法是能夠在協議的任何時候接受 NotificationResponse。
在查詢處理過程中,前端可能請求取消查詢。取消請求不會直接傳送到後端開啟的連線上,原因在於實現效率:我們不希望後端在查詢處理過程中不斷檢查前端的新輸入。取消請求應該相對不頻繁,因此我們使其稍微繁瑣一些,以避免在正常情況下受到影響。
要發出取消請求,前端會開啟一個新連線到伺服器併發送一個 CancelRequest 訊息,而不是通常會透過新連線傳送的 StartupMessage 訊息。伺服器將處理此請求然後關閉連線。出於安全原因,不會對 CancelRequest 訊息做出直接響應。
除非 CancelRequest 訊息包含在連線啟動期間傳遞給前端的相同金鑰資料(PID 和金鑰),否則它將被忽略。如果請求與當前正在執行的後端匹配 PID 和金鑰,則當前查詢的處理將被中止。(在現有實現中,這是透過向處理查詢的後端程序傳送一個特殊訊號來完成的。)
取消訊號可能有效,也可能無效——例如,如果它在後端完成查詢處理後到達,則無效。如果取消生效,將導致當前命令提前終止並附帶錯誤訊息。
總而言之,出於安全和效率的考慮,前端沒有直接的方法來知道取消請求是否成功。它必須繼續等待後端響應查詢。發出取消請求只是增加了當前查詢很快完成的可能性,並增加了它以錯誤訊息失敗而不是成功的可能性。
由於取消請求是透過與伺服器的新連線傳送的,而不是透過常規的前端/後端通訊鏈路傳送的,因此任何程序都可以發出取消請求,而不僅僅是將被取消查詢的前端。這可能為構建多程序應用程式提供額外的靈活性。它也帶來了安全風險,因為未經授權的人員可能會嘗試取消查詢。透過要求在取消請求中提供動態生成的金鑰來解決安全風險。
正常、優雅的終止過程是前端傳送 Terminate 訊息並立即關閉連線。收到此訊息後,後端會關閉連線並終止。
在極少數情況下(例如管理員命令的資料庫關閉),後端可能會在沒有前端請求的情況下斷開連線。在這種情況下,後端將在關閉連線之前嘗試傳送一個錯誤或通知訊息,說明斷開連線的原因。
其他終止場景源於各種故障情況,例如一端或另一端的核心轉儲、通訊鏈路丟失、訊息邊界同步丟失等。如果前端或後端看到連線意外關閉,它應該進行清理並終止。前端可以選擇透過重新聯絡伺服器來啟動一個新後端,如果它不想終止自身。如果收到無法識別的訊息型別,也建議關閉連線,因為這可能表示訊息邊界同步丟失。
對於正常或異常終止,任何未提交的事務都將被回滾,而不是提交。但應注意,如果在非 SELECT
查詢正在處理時前端斷開連線,後端很可能會在注意到斷開連線之前完成查詢。如果查詢不在任何事務塊(BEGIN
... COMMIT
序列)中,那麼在其結果被提交之前,斷開連線可能會被識別。
如果 PostgreSQL 使用SSL支援構建,則可以使用SSL對前端/後端通訊進行加密。這在可能捕獲會話流量的攻擊者的環境中提供了通訊安全。有關使用SSL加密 PostgreSQL 會話的更多資訊,請參閱 第 18.9 節。
要啟動一個SSL加密連線,前端最初會發送一個 SSLRequest 訊息而不是 StartupMessage。然後,伺服器會響應一個包含 S
或 N
的單個位元組,分別表示它願意或不願意執行SSL。如果前端對響應不滿意,它可能會在此處關閉連線。要繼續處理 S
,請執行SSL啟動握手(此處未描述,是SSL規範的一部分)與伺服器。如果成功,則繼續傳送常規的 StartupMessage。在這種情況下,StartupMessage 和所有後續資料都將是SSL-加密的。要繼續處理 N
,請傳送常規的 StartupMessage 並繼續不加密。(或者,在收到 N
響應後發出 GSSENCRequest 訊息,嘗試使用GSSAPI而不是SSL.)
。前端還應準備好處理來自伺服器的對 SSLRequest 的 ErrorMessage 響應。前端不應向用戶/應用程式顯示此錯誤訊息,因為伺服器尚未經過身份驗證(CVE-2024-10977)。在這種情況下,必須關閉連線,但前端可以選擇開啟新連線並繼續而不請求SSL.
。當SSL加密可以執行時,伺服器應只發送單個 S
位元組,然後等待前端啟動SSL握手。如果此時可以讀取其他位元組,則可能意味著中間人正在嘗試進行緩衝區填充攻擊(CVE-2021-23222)。前端應被編碼為在將套接字交給其 SSL 庫之前只從套接字讀取一個位元組,或者如果發現讀取了其他位元組則將其視為協議違規。
同樣,伺服器期望客戶端在收到伺服器對SSL請求的單個位元組響應之前,不要開始SSL協商。如果客戶端在未等待收到伺服器響應的情況下立即開始SSL協商,它可以減少一個往返的連線延遲。然而,這會以無法處理伺服器對SSL請求傳送負響應的情況為代價。在這種情況下,伺服器將簡單地斷開連線,而不是繼續使用 GSSAPI、未加密連線或協議錯誤。
初始 SSLRequest 也可以用於正在開啟以傳送 CancelRequest 訊息的連線。
還有第二個可選方法來啟動SSL加密。伺服器將識別那些在沒有任何前置 SSLRequest 資料包的情況下立即開始SSL協商的連線。一旦建立SSL連線,伺服器將期望一個常規的 startup-request 資料包,並在加密通道上繼續協商。在這種情況下,任何其他加密請求都將被拒絕。此方法不適用於通用工具,因為它無法協商最佳可用連線加密或處理未加密的連線。然而,它對於伺服器和客戶端一起受控的環境很有用。在這種情況下,它可以避免一個往返的延遲,並允許使用依賴標準SSL連線的網路工具。使用這種方式的SSL連線時,客戶端必須使用 RFC 7301 定義的 ALPN 擴充套件來防止協議混淆攻擊。PostgreSQL 協議是 IANA TLS ALPN 協議 ID 登錄檔中註冊的“postgresql”。
雖然協議本身不提供伺服器強制SSL加密的方法,但管理員可以透過身份驗證檢查的副產品將伺服器配置為拒絕未加密的會話。
如果 PostgreSQL 使用GSSAPI支援構建,則可以使用GSSAPI對前端/後端通訊進行加密。這在可能捕獲會話流量的攻擊者的環境中提供了通訊安全。有關使用GSSAPI,請參閱 第 18.10 節。
要啟動一個GSSAPI加密連線,前端最初會發送一個 GSSENCRequest 訊息而不是 StartupMessage。然後,伺服器會響應一個包含 G
或 N
的單個位元組,分別表示它願意或不願意執行GSSAPI加密。如果前端對響應不滿意,它可能會在此處關閉連線。要繼續處理 G
,使用 RFC 2744 或同等文件中討論的 GSSAPI C 繫結,透過呼叫 gss_init_sec_context()
並將結果傳送到伺服器來執行GSSAPI初始化,從空輸入開始,然後每次從伺服器獲得結果時使用,直到它返回無輸出。傳送 gss_init_sec_context()
的結果到伺服器時,將訊息長度作為網路位元組序的四位元組整數前置。要繼續處理 N
,請傳送常規的 StartupMessage 並繼續不加密。(或者,在收到 N
響應後發出 SSLRequest 訊息,嘗試使用SSL而不是GSSAPI.)
。前端還應準備好處理來自伺服器的對 GSSENCRequest 的 ErrorMessage 響應。前端不應向用戶/應用程式顯示此錯誤訊息,因為伺服器尚未經過身份驗證(CVE-2024-10977)。在這種情況下,必須關閉連線,但前端可以選擇開啟新連線並繼續而不請求GSSAPI加密。
。當GSSAPI加密可以執行時,伺服器應只發送單個 G
位元組,然後等待前端啟動GSSAPI握手。如果此時可以讀取其他位元組,則可能意味著中間人正在嘗試進行緩衝區填充攻擊(CVE-2021-23222)。前端應被編碼為在將套接字交給其 GSSAPI 庫之前只從套接字讀取一個位元組,或者如果發現讀取了其他位元組則將其視為協議違規。
初始 GSSENCRequest 也可以用於正在開啟以傳送 CancelRequest 訊息的連線。
一旦GSSAPI加密成功建立,請使用 gss_wrap()
來加密常規的 StartupMessage 和所有後續資料,將 gss_wrap()
結果的長度作為四位元組整數以網路位元組序前置到實際加密的負載。請注意,伺服器僅接受客戶端小於 16kB 的加密資料包;客戶端應使用 gss_wrap_size_limit()
來確定適合此限制的未加密訊息的大小,較大的訊息應分解為多個 gss_wrap()
呼叫。典型的分段是 8kB 的未加密資料,導致加密資料包略大於 8kB 但遠小於 16kB 的最大值。伺服器應傳送小於 16kB 的加密資料包給客戶端。
雖然協議本身不提供伺服器強制GSSAPI加密的方法,但管理員可以透過身份驗證檢查的副產品將伺服器配置為拒絕未加密的會話。
如果您在文件中看到任何不正確、與您在使用特定功能時的體驗不符或需要進一步說明的內容,請使用 此表單 報告文件問題。