49.2. 消息流

本节描述消息流,以及每种消息类型的语意(每种消息的准确表现细节在第 49.5 节里)。 因连接的状态不同,存在几种不同的子协议:启动、查询、函数调用、COPY 、结束。 还有用于异步操作(包括通知响应和命令取消)的特殊规定,这些异步操作可能在启动阶段过后的任何时间产生。

49.2.1. 启动

要开始一个会话,前端打开一个与服务器的连接并且发送一个启动消息。 这个消息包括用户名以及用户希望与之连接的数据库;它还标识要使用的特定的协议版本。 (另外,启动消息可以包括用于运行时参数的额外设置。) 服务器然后就使用这些信息以及它的配置文件的内容(比如 pg_hba.conf)以判断这个连接是否可以接受,以及需要什么样的额外的认证(如果有)。

然后服务器就发送合适的认证请求消息,前端必须用合适的认证响应消息来响应(比如一个口令)。 对所有的认证方法,除了GSSAPI和SSPI,最多只有一次请求和响应。 有些认证方法则根本不需要前端的响应,因此就没有认证请求发生。 对于GSSAPI和SSPI,为了完成认证则可能需要多次数据包的交换。

认证周期的结束要么是服务器拒绝连接(ErrorResponse),要么是服务器发送AuthenticationOk消息。

这个阶段来自服务器可能消息是:

ErrorResponse

连接请求被拒绝。然后服务器马上关闭连接。

AuthenticationOk

认证信息的交换成功完成。

AuthenticationKerberosV5

现在前端必须与服务器进行一次 KerberosV5 认证对话(是Kerberos规范的一部分,在这里没有描述)。 如果对话成功,服务器响应一个 AuthenticationOk消息,否则它响应一个 ErrorResponse消息。 现在已经不再支持这个了。

AuthenticationCleartextPassword

现在前端必须发送一个包含明文口令的 PasswordMessage包。 如果这是正确的口令,服务器用一个 AuthenticationOk 包响应,否则它响应一个 ErrorResponse 包。

AuthenticationMD5Password

现在前端必须发送一个包含用 MD5 加密的口令(包括用户名)的 PasswordMessage ,加密时使用在 AuthenticationMD5Password 消息里指定的 4 字节随机盐粒。 如果这是正确口令,服务器用一个 AuthenticationOk 响应,否则它用一个 ErrorResponse 响应。 实际的PasswordMessage可以通过SQL语句concat('md5',md5(concat(md5(concat(password, username)), random-salt)))计算得到。 (注意md5() 函数的返回值是16进制表示的字符串。)

AuthenticationSCMCredential

这个响应只会在那些支持 SCM 信任消息的本地 Unix 域连接上出现。 前端必须发出一条 SCM 信任消息然后发送一个数据字节。 (数据字节的内容并不会被关心;它的作用只是确保服务器等待了足够长的时间来接受信任信息。) 如果信任是可以接受的,那么服务器用 AuthenticationOk 响应,否则用 ErrorResponse 响应。 (只有9.1以前的服务器才可能发出这个消息,并且这个消息最终可能会被从协议中删除。)

AuthenticationGSS

现在前端必须发起一个GSSAPI协商。 作为响应前端将随着GSSAPI数据流的最初部分发送一个PasswordMessage包。 如果需要进一步的消息,服务端将以AuthenticationGSSContinue作为响应。

AuthenticationSSPI

现在前端必须发起一个SSPI协商。 作为响应前端将随着SSPI数据流的最初部分发送一个PasswordMessage包。 如果需要进一步的消息,服务端将以AuthenticationGSSContinue作为响应。

AuthenticationGSSContinue

这个消息包含GSSAPI或SSPI协商的上一个步骤(AuthenticationGSS, AuthenticationSSPI或前一个AuthenticationGSSContinue)的响应数据。 如果这个消息中的GSSAPI或SSPI数据指示需要更多的数据以完成认证过程,前端必须用另一个PasswordMessage包发送这些数据。 如果通过这个消息GSSAPI或SSPI认证已完成,服务端下次将发送AuthenticationOk指示认证成功或ErrorResponse指示认证失败。

如果前端不支持服务器要求的认证方式,那么它应该马上关闭连接。

在收到 AuthenticationOk 包之后,前端必须等待来自后端的更多消息。 在这个阶段会启动一个后端进程,而前端只是一个感兴趣的看热闹的。 启动尝试仍然有可能失败 (ErrorResponse),但是通常情况下,后端将发送一些 ParameterStatus 消息、BackendKeyData 、最后是 ReadyForQuery 。

在这个阶段,后端将尝试应用任何在启动消息里给出的额外的运行时参数设置。 如果成功,这些值将成为会话的缺省值。错误将导致 ErrorResponse 并退出。

这个阶段来自后端的可能消息是:

BackendKeyData

这个消息提供了密钥(secret-key)数据,前端如果想要在稍后发出取消的请求,则必须保存这个数据。 前端不应该响应这个消息,但是应该继续侦听等待 ReadyForQuery 消息。

ParameterStatus

这个消息告诉前端有关后端参数的当前(初始化)设置,比如 client_encodingDateStyle 等。 前端可以忽略这个消息,或者记录其设置用于将来使用;参阅第 49.2.6 节获取更多细节。 前端不应该响应这个消息,而是应该继续侦听 ReadyForQuery 消息。

ReadyForQuery

后端启动成功,前端现在可以发出命令。

ErrorResponse

后端启动失败。在发送完这个消息之后连接被关闭。

NoticeResponse

发出了一个警告消息。前端应该显示这个消息,并且继续等待 ReadyForQuery 或 ErrorResponse 。

后端在每个查询循环后都会发出一个相同的 ReadyForQuery 消息。 前端可以合理地认为 ReadyForQuery 是一个查询循环的开始, 或者认为 ReadyForQuery 是启动阶段和每个随后查询循环的结束,具体是哪种情况取决于前端的编码需要。

49.2.2. 简单查询

一个查询循环是由前端发送一条 Query 消息给后端进行初始化的。 这条消息包含一个用文本字符串表示的 SQL 命令(或者一些命令)。 后端根据查询命令字符串的内容发送一条或者更多条响应消息给前端,并且最后是一条 ReadyForQuery 响应消息。 ReadyForQuery 通知前端它可以安全地发送新命令了。 (实际上前端不必在发送其它命令之前等待 ReadyForQuery ,但是这样一来,前端必须负责区分早先发出的命令失败,而稍后发出的命令成功的情况。)

从后端来的可能的消息是:

CommandComplete

一个 SQL 命令正常结束

CopyInResponse

后端已经准备好从前端拷贝数据到一个表里面去。又见第 49.2.5 节

CopyOutResponse

后端已经准备好从一个表里拷贝数据到前端里面去。又见第 49.2.5 节

RowDescription

表示为了响应一个 SELECT, FETCH 等的查询,将要返回一个行。 这条消息的内容描述了这行的字段布局。这条消息后面将跟着每个返回给前端的行一个的 DataRow 消息。

DataRow

SELECT, FETCH 等查询返回的结果集中的一行。

EmptyQueryResponse

识别了一个空的查询字符串。

ErrorResponse

出错了。

ReadyForQuery

查询字符串的处理完成。发送一个独立的消息来标识这个是因为查询字符串可能包含多个 SQL 命令。 (CommandComplete 只是标记一条 SQL 命令处理完毕,而不是整个字符串。) ReadyForQuery 总会被发送,不管是处理成功结束还是产生错误。

NoticeResponse

发送了一个与查询有关的警告消息。注意警告消息是附加在其它响应上的,也就是说,后端将继续处理该命令。

SELECT(或其它返回结果集的查询,比如 EXPLAINSHOW)查询的响应通常包含 RowDescription ,零个或者多个 DataRow 消息,以及最后的 CommandComplete 。 从或者到前端的 COPY 使用第 49.2.5 节里提到的特殊的协议。所有其它查询类型通常只生成一个 CommandComplete 消息。

因为查询字符串可能包含若干个查询(用分号分隔),所以在后端完成查询字符串的处理之前可能有好几个这样的响应序列。 如果整个字符串已经处理完,后端已经准备好接受新查询字符串的时候则发出 ReadyForQuery 消息。

如果收到一个完全空(除了空白之外没有内容)的查询字符串,那么响应是一条 EmptyQueryResponse 后面跟着 ReadyForQuery 。

在出现错误的时候,发出一个 ErrorResponse 消息,后面跟着 ReadyForQuery 。 查询字符串的所有后继的处理都被 ErrorResponse 中止(即使里面还有查询也这么干)。 请注意这些事情可能在处理一个查询产生的消息序列的中途发生。

在简单查询模式,检索出来的数值的格式总是文本的,除非给出的命令是从一个声明了BINARY选项的游标上的FETCH 。 在这种情况下,检索出来的数值是二进制格式的。在 RowDescription 消息里给出的格式代码告诉用了哪种格式。

前端在等待其它类型的消息时必须准备接收 ErrorResponse 和 NoticeResponse 消息。 参阅第 49.2.6 节后端因为外部的事件可能生成的消息。

建议的方法是把前端代码写成状态机的风格,它可以在任何时刻接受任何有意义的消息,而不是写成假设消息的准确序列的代码。

49.2.3. 扩展查询

扩展的查询协议把上面描述的简单协议分裂成若干个步骤。准备的步骤可以多次复用以提高效率。 另外,还可以获得额外的特性,比如把数据值作为独立的参数提供的可能性,而不是把它们直接插入一个查询字符串。

在扩展的协议里,前端首先发送一个 Parse 消息,它包含一个文本查询字符串,另外还有一些有关参数占位符的数据类型的信息,以及一个最终预备语句对象的名字(一个空字符串选择未命名的预备语句)。 响应要么是一个 ParseComplete 要么是 ErrorResponse 。参数数据类型可以用 OID 来声明;如果没有给出,那么分析器将试图用它对付无类型的字符串常量的方法来推导其数据类型。

注意: 一个参数数据类型可以通过设置为零,或者让参数类型 OID 的数目比查询字符串里的参数符号($n)的数目少的方法不予声明。 另外一个特例是参数的类型可以声明为 void(也就是伪类型 void 的 OID)。 这是为了允许用于某些函数参数的参数符号实际上是 OUT 参数。 通常情况下,没有什么环境会用到 void 参数,但是如果在函数的参数列表里出现了这么一个参数符号,那么它实际上会被忽略。 比如,一个像这样的函数调用:foo($1,$2,$3,$4),如果$3$4声明为类型是 void ,那么这个函数调用可以匹配一个带有两个 IN 和两个 OUT 参数的函数。

注意: 在一个 Parse 消息里包含的查询字符串不能包含超过一个 SQL 语句;否则就会报告一个语法错误。 这个限制在简单查询协议中并不存在,但是它存在于扩展的协议中,因为允许预备语句或者入口包含多个命令将导致协议过度地复杂。

如果成功创建了一个命名的预备语句对象,那么它将持续到当前会话结束,除非明确删除。 一个未命名的预备语句只持续到下一个声明未命名的语句的 Parse 语句发出为止(请注意一个简单的查询消息也删除未命名语句)。 命名的预备语句必须明确地关闭,然后才能用一个 Parse 消息重新定义,但是未命名的语句并不要求这个动作。 命名的预备语句也可以在 SQL 命令级创建和访问,方法是使用 PREPAREEXECUTE

只要预备语句还存在,那么就可以使用 Bind 消息很容易地使之进入执行状态。 Bind 消息给出源预备语句的名字(空字符串表示未命名的预备语句),目标入口的名字(空字符串表示未命名的入口),以及用于那些在预备语句中出现的所有参数占位符的数值。 提供的参数集必须匹配那些预备语句需要的东西。(如果你在 Parse 消息里声明任何 void 参数,那么在 Bind 消息里给它们传递 NULL 值。) Bind 还声明用于查询返回的任何数据的格式;格式可以一次声明,也可以每个字段进行声明。响应要么是 BindComplete 要么是 ErrorResponse 。

注意: 输出的格式是文本还是二进制是由 Bind 里给出的格式代码决定的,不管用的是什么 SQL 命令。 在使用扩展的查询协议的时候,游标声明里的 BINARY 属性是不起作用的。

典型的查询计划在处理Bind消息后生成。 预备语句如果不包含参数,或者被反复执行的情况下,服务端可能会保存创建好的查询计划并在后续的针对同样的Bind消息中重用它。 但是,只有确保这样作成的通用的查询计划不至于比依赖于特定参数值的查询计划效率差太多的情况下,服务端才会这么做。 就协议而言这些是透明的。

如果成功创建了一个命名的入口对象,那么它将持续到当前会话结束,除非明确删除。 一个未命名的入口只持续到事务结束或下一个声明未命名的入口的 Bind消息发出为止(请注意一个简单的查询消息也删除未命名入口)。 命名的入口必须明确地关闭,然后才能用另一个 Bind 消息重新定义,但是未命名的语句并不要求这个动作。 命名的入口也可以在 SQL 命令级创建和访问,方法是使用DECLARE CURSORFETCH

只要存在一个入口,那么就可以用一个 Execute 消息执行它。 Execute 消息声明入口的名字(空字符串表示未命名入口)和一个最大的结果行计数(零表示"抓取所有行")。 结果行计数只对包含返回结果集的入口有意义;在其它情况下,该命令总是执行到结束,而行计数会被忽略。 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 消息并不导致任何特定的输出的生成,但是强制后端发送任何还在它的输出缓冲区中停留的数据。 Flush 必须在除 Sync 外的任何扩展查询命令后面发出(如果前端希望在发出更多的命令之前检查该命令的结果的话)。 如果不 Flush ,后端返回的消息将组合成最小可能的数据包个数,以减少网络负荷。

注意: 简单查询消息大概等于一系列使用未命名的预备语句和无参数的入口对象的 Parse 、Bind 、入口 Describe 、Execute 、Close 、Sync 。 一个区别是它会在查询字符串中接受多个 SQL 语句,并在会话中为每个语句自动执行绑定/描述/执行序列。 另外一个区别是它不会返回 ParseComplete 、Bindcomplete 、CloseComplete 、NoData 消息。

49.2.4. 函数调用

函数调用子协议允许客户端请求一个对存在于数据库pg_proc系统表中的任意函数的直接调用。 客户端必须在该函数上有执行的权限。

注意: 函数调用子协议是一个遗留的特性,在新代码里最好避免用它。 类似的结果可以通过设置一个SELECT function($1, ...)预备语句获得。 这样函数调用循环就可以用 Bind/Execute 代替。

一个函数调用循环是由前端向后端发送一条 FunctionCall 消息初始化的。 然后后端根据函数调用的结果发送一条或者更多响应消息,并且在最后是一条 ReadyForQuery 响应消息。 ReadyForQuery 通知前端它可以安全地发送一条新的查询或者函数调用了。

从后端来的可能的消息是:

ErrorResponse

发生了一个错误。

FunctionCallResponse

函数调用完成并且在消息中返回一个结果。 (请注意,函数调用协议只能处理单个标量结果,不能处理行类型或者结果集。)

ReadyForQuery

函数调用处理完成。ReadyForQuery 将总是被发送,不管是成功完成处理还是发生了一个错误。

NoticeResponse

发出了一条有关该函数调用的警告消息。通知是附加在其它响应上的,也就是说,后端将继续处理命令。

49.2.5. COPY操作

COPY命令允许在服务器和客户端之间高速的大批量数据传输。 拷贝入和拷贝出操作每个都把连接切换到一个独立的子协议中,并且持续到操作结束。

拷贝入模式(数据传输到服务器)是在后端执行一个COPY FROM STDIN语句的时候初始化的。 后端发送一个 CopyInResponse 消息给前端。 前端应该发送零条或者更多 CopyData 消息,形成一个输出数据的流。 (消息的边界和行的边界没有任何相关性要求,尽管通常那就是合理的边界选择。) 前端可以通过发送一个 CopyDone 消息来终止拷贝入操作(允许成功终止),也可以发出一个 CopyFail 消息(它将导致 COPY 语句带着错误失败)。 然后后端就恢复回它在 COPY 开始之前的命令处理模式,可能是简单查询协议,也可能是扩展查询协议。 然后它会发送 CommandComplete(如果成功)或者 ErrorResponse(如果失败)。

如果在拷贝入模式下后端检测到了错误(包括接受接收到 CopyFiail 消息,或者是任何除了 CopyData 或者 CopyDone 之外的前端消息),那么后端将发出一个 ErrorResponse 消息。 如果COPY 命令是通过一个扩展的查询消息发出的,那么后端从现在开始将抛弃前端消息,直到一个 Sync 消息到达,然后它将发出 ReadyForQuery 并且返回到正常的处理中。 如果COPY 命令是在一个简单查询消息里发出的,那么该消息剩余部分被丢弃然后发出 ReadyForQuery 消息。 不管是哪种情况,任何前端发出的 CopyData, CopyDone, CopyFail 消息都将被简单地抛弃。

后端将会忽略拷贝入模式时接收到的 Flush 和 Sync 消息。 收到任何其它非拷贝消息,会引发错误,如前所述这样会导致退出拷贝入状态。 (Flush 和 Sync 的例外是为了方便客户端库在执行 Execute 消息后始终发送 Flush 或 Sync 而不检查所执行的命令是否是一个 COPY FROM STDIN 命令。)

拷贝出模式(数据从服务器发出)是在后端执行一个COPY TO STDOUT语句的时候初始化的。 后端发出一个 CopyOutResponse 消息给前端,后面跟着零或者多个 CopyData 消息(总是每行一个),然后跟着 CopyDone 。 然后后端回退到它在COPY开始之前的命令处理模式,然后发送 CommandComplete 。 前端不能退出传输(除非是关闭连接或者发出一个 Cancel 请求),但是它可以抛弃不需要的 CopyData 和 CopyDone 消息。

在拷贝出模式中,如果后端检测到错误,那么它将发出一个 ErrorResponse 消息并且回到正常的处理。 前端应该把收到 ErrorResponse当作终止拷贝出模式的标志。

NoticeResponse和ParameterStatus消息有可能夹在CopyData消息间出现。 前端必须处理这种情况,并且也应该准备好处理其它异步消息(参阅第 49.2.6 节)。 除此以外,任何CopyData和CopyDone以外的消息应该被视作拷贝出模式的中止。

还有一个拷贝相关的模式,称作双向拷贝,它允许从到服务端的高速的批量数据传输。 双向拷贝模式由处于walsender模式的后端执行一条START_REPLICATION语句初始化。 后端发送一个CopyBothResponse给前端。然后后端和前端可能都发送CopyData消息,直到有一方发出一个CopyDone消息。 客户端发送CopyDone消息后,连接从双向拷贝模式切换到拷贝出模式,并且客户端不应该再发出任何CopyData消息。 相似的,如果服务端发出CopyDone消息,连接进入拷贝入模式,并且服务端不应该再发出任何CopyData消息。 两边都发送了CopyDone消息后,拷贝模式终止,后端返回到命令处理模式。 在双向拷贝模式中,如果后端检出错误,将发出一个ErrorResponse消息,丢弃任何来自前端的消息直到收到Sync消息,然后发送ReadyForQuery消息并返回到普通的处理模式。 前端应把收到ErrorResponse消息作为在两个方向上都终止拷贝的标志,这个时候不应该发出CopyDone消息。 请参阅第 49.3 节获得更多有关双向拷贝模式下子协议传输的信息。

CopyInResponse,CopyOutResponse和 CopyBothResponse 消息包含了告诉前端每行的字段数以及每个字段使用的格式代码的信息。 (就目前的实现而言,某个COPY操作的所有字段都使用同样的格式,但是消息设计并不做这个假设。)

49.2.6. 异步操作

有几种情况下后端会发送一些并非由特定的前端的命令流提示的消息。 在任何时候前端都必须准备处理这些消息,即使是并未涉及到查询处理的时候。 至少,应该在开始读取查询响应之前检查这些情况。

NoticeResponse 消息有可能是因为外部的活动而生成的;比如,如果数据库管理员进行一次"快速"数据库关闭,那么后端将在关闭连接之前发送一个 NoticeResponse 来通知这件事。 因此,前端应该总是准备接受和显示 NoticeResponse 消息,即使连接表面上是空闲的时候也如此。

如果后端认为前端应该知道的任何参数的实际值发生了变化,那么都会产生 ParameterStatus 消息。 这些最经常发生的地方是前端执行的一个SET命令的响应,并且这个时候实际上是同步(但是也有可能是数据库管理员改变了配置文件然后SIGHUP了服务器导致的参数状态的变化)。 同样,如果一个SET命令回滚,那么也会生成合适的 ParameterStatus 消息以报告当前有效的数值。

目前,系统内有一套会生成 ParameterStatus 消息的硬编码的参数:他们是 server_versionserver_encodingclient_encodingapplication_nameis_superusersession_authorizationDateStyleIntervalStyleTimeZoneinteger_datetimes以及standard_conforming_strings。 (8.0以前的版本不会报告server_encodingTimeZoneinteger_datetimes。 8.1以前的版本不会报告standard_conforming_strings。 8.4以前的版本不会报告IntervalStyle。 9.0以前的版本不会报告application_name。) 请注意server_versionserver_encodinginteger_datetimes是伪参数,启动后不能修改。 这个参数集合可能在将会改变,或者甚至是变成可以配置的。因此,前端应该简单地忽略那些它不懂或者不关心的 ParameterStatus 。

如果前端发出一个LISTEN命令,那么为同一个通道名执行了NOTIFY命令后,将收到后端发来的一个 NotificationResponse 消息(不要和 NoticeResponse 混淆)。

注意: 目前,NotificationResponse 只能在一个事务外面发送,因此它将不会在一个命令响应序列中间出现,但是它可能在 ReadyForQuery 之前出现。 不过,在前端逻辑中做上述假设是不明智的。好的做法是在协议的任何点上都可以接受 NotificationResponse 。

49.2.7. 取消正在处理的请求

在一条查询正在处理的时候,前端可能请求取消这个查询。 这样的取消请求不是直接通过打开的当前连接发送给后端的,这么做是因为实现的有效性:不希望后端在处理查询的过程中不停地检查前端来的输入。 取消请求应该相对而言比较少见,所以把取消做得稍微笨拙一些,以便不影响正常状况的性能。

要发送一条取消请求,前端打开一个与服务器的新连接并且发送一条 CancelRequest 消息,而不是通常在新连接中经常发送的 StartupPacket 消息。 服务器将处理这个请求然后关闭连接。出于安全原因,对取消请求消息不做直接的响应。

除非 CancelRequest 消息包含与连接启动过程中传递给前端的相同的键数据(PID 和安全键字),否则它将被忽略。 如果该请求匹配当前运行着的后端的 PID 和安全键字,则退出当前查询的处理(目前的实现里采用的方法是向正在处理该查询的后端进程发送一个特殊的信号)。

取消信号可能有也可能没有任何作用。 例如,如果它在后端已经完成了查询的处理后到达,那么它就没有作用。 如果取消起作用了,其结果是当前命令带着一个错误消息提前退出。

这么做是对安全和有效性通盘考虑的结果,前端没有直接的方法获知一个取消请求是否成功。 它必须继续等待后端对查询响应。执行取消仅仅是增加了当前查询快些结束的可能性,以及增加了当前查询会带着一条错误消息失败而不是成功执行的可能性。

因为取消请求是通过新的连接发送给服务器而不是通过通常的前/后端通讯连接,所以取消请求可能是任意进程执行的,而不仅仅是要取消查询的前端。 这样可能对创建多进程应用有某种灵活性的好处。但是同时这样也带来了安全风险,因为这样任何一个非认证用户都可能试图取消查询。 这个安全风险通过要求在取消请求中提供一个动态生成的安全键字排除。

49.2.8. 终止

通常,优雅的终止过程是前端发送一条 Terminate消息并且立刻关闭连接。一旦收到这个消息,后端马上关闭连接并且退出。

在少数情况下(比如通过一个管理员命令关闭数据库),后端可能在没有任何前端请求的情况下断开连接。 在这种情况下,后端将在它断开连接之前尝试发送一个错误或者通知消息,给出断开的原因。

其它终止的情况发生在各种失效的场合,比如某一方的内核转储,失去通讯链路,丢失了消息边界同步等。 不管是前端还是后端看到了一个意外的连接关闭,那么它应该清理现场并且终止。 如果前端不想终止自己,那么它可以通过再次连接服务器的方式重启一个新的后端。 如果收到了一个无法识别的消息,那么也建议关闭连接,因为出现这种情况可能意味着是丢失了消息边界的同步。

不管是正常还是异常的终止,任何打开的事务都会回滚,而不是提交。 不过,应该注意的是如果一个前端在一个非SELECT查询正在处理的时候断开,那么后端很可能在注意到断开之前先完成查询的处理。 如果查询处于任何事务块之外((BEGIN ... COMMIT序列),那么其结果很可能在得知断开之前被提交。

49.2.9. SSL会话加密

如果编译PostgreSQL的时候打开了SSL支持,那么前后端通讯就可以用SSL加密。 这样就提供了一种在攻击者可能捕获会话通讯数据包的环境下保证通讯安全的方法。 有关使用SSL加密PostgreSQL会话的更多信息,请参阅第 17.9 节

要开始一次SSL加密连接,前端先是发送一个 SSLRequest 消息,而不是 StartupMessage 。 然后服务器以一个包含SN的字节响应,分别表示它愿意还是不愿意进行SSL。 如果前端对响应不满意,那么它可以关闭连接。 要在S之后继续,那么先进行与服务器的SSL启动握手(没有在这里描述,这是SSL规范的一部分)。 如果这些成功了,那么继续发送普通的 StartupMessage 。 这种情况下,StartupMessage 和所有随后的数据都将由SSL加密。 要在N之后继续,则发送普通的 StartupMessage 然后不带加密进行处理。

前端应该也准备处理一个来自服务器的给 SSLRequest 的 ErrorMessage 响应。 这种情况只有在那些SSL支持被加入到PostgreSQL以前的服务上才会出现。 (这样的服务器太古老了,很可能根本就不存在。) 在这种情况下,连接必需关闭,但是前端可以选择打开一个新的连接然后不带SSL进行连接。

一个初始化的 SSLRequest 也可以用于为了发送一条 CancelRequest 消息而打开的连接中。

如果协议本身并未提供某种方法强制SSL加密,那么管理员可以把服务器配置为拒绝未加密的会话,这是认证检查的一个副产品。