4.2. 协议

本节描述信息流.根据联接的状态不同有四种类型的流: 启动(startup),查询(query),函数调用(function call)和结束(termination).还有用于通知响应和命令取消的特殊信息,这些特殊信息可能在启动阶段过后的任何时间产生.

4.2.1. 启动

启动分成认证阶段和后端启动阶段.

开始时,前端发送一个 StartupPacket (启动包). postmaster 利用这个信息和 pg_hba.conf(5) 文件的内容决定这个前端必须使用哪种认证方式. 然后 postmaster 用下面信息之一响应:

ErrorResponse

然后 postmaster 马上关闭联接.

AuthenticationOk

然后 postmaster 转交给后端.postmaster 不再参与以后的通讯.

AuthenticationKerberosV4

然后前端必须与 postmaster 进行一次 Kerberos V4 认证对话(在这里没有描述). 如果对话成功,postmaster 响应一个 AuthenticationOk (认证成功)信息, 否则它响应一个 ErrorResponse (错误响应).

AuthenticationKerberosV5

然后前端必须与 postmaster 进行一次 Kerberos V5 认证对话(在这里没有描述). 如果对话成功,postmaster响应一个 AuthenticationOk (认证成功)信息, 否则它响应一个 ErrorResponse (错误响应).

AuthenticationUnencryptedPassword

然后前端必须发送一个 UnencryptedPasswordPacket (未加密口令)包. 如果这是正确的口令,postmaster用一个 AuthenticationOk 包响应, 否则它响应一个 ErrorResponse 包.

AuthenticationEncryptedPassword

然后前端必须发送一个 EncryptedPasswordPacket (加密口令)包. 如果这是正确口令,postmaster 用一个AuthenticationOk 响应, 否则它用一个 ErrorResponse 响应.

如果前端不支持 postmaster 要求的认证方式,那么它应该马上关闭联接.

在发送完 AuthenticationOk 包之后,postmaster 试图运行一个后端进程. 因为这个过程可能失败,或者后端可能在启动过程中失败, 前端必须等待后端确认成功启动. 这时前端不应该发送任何信息.在这个阶段从后端来的信息可能是:

BackendKeyData

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

ReadyForQuery

后端启动成功,前端现在可以发出查询或者函数调用消息.

ErrorResponse

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

NoticeResponse

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

后端在每个查询循环后都会发出一个相同的 ReadyForQuery 消息. 前端可以认为 ReadyForQuery 是一个查询循环的开始 (而 BackendKeyData 表明启动阶段的成功完成), 或者认为 ReadyForQuery 是启动阶段和每个随后查询循环的结束, 这些取决于前端的编码需要.

4.2.2. 查询

一个查询循环是由前端发送一条 Query 消息给后端初始化的. 后端根据查询命令字串的内容发送一条或者更多条响应消息给前端, 并且最后是一条 ReadyForQuery 响应信息. ReadyForQuery 通知前端它可以安全地发送新查询或者函数调用给后端了.

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

CompletedResponse

一个正常结束的 SQL 命令.

CopyInResponse

后端已经准备好从前端拷贝数据到一个关系里面去. 然后前端应该发送一条 CopyDataRows 消息. 然后后端用一个带有标记 "COPY" 的 CompletedResponse 消息响应.

CopyOutResponse

后端已经准备好从一个关系拷贝数据到前端里面去. 然后它会发送一条 CopyDataRows 消息. 最后后端发送一个带有标记 "COPY" 的 CompletedResponse 消息.

CursorResponse

查询可以是一条 insert(l),delete(l),update(l),fetch(l) 或一个 select(l) 命令. 如果事务被终止,那么后端发送一条带有标记 "*ABORT STATE*" 的 CompletedResponse 消息.否则发送下面的响应.

对一条 insert(l) 命令,后端发送一条带有 "INSERT oid rows" 标记的 CompletedResponse 消息,这里的 rows 是插入的行数,而 oid 在插入的行数 rows 为 1 时是插入行的对象标识(OID),否则 oid 为 0.

对一条 delete(l) 命令,后端发送一条带有 "DELETE rows" 标记的 CompletedResponse 消息, 这里 rows 是删除的行数.

对一条 update(l) 命令,后端发送一条带有 "UPDATE rows" 标记的 CompletedResponse 消息, 这里 rows 是更新的行数.

对于一条 fetch(l) 或 select(l) 命令, 后端发送一条 RowDescription 消息. 然后跟着一条 AsciiRow 或BinaryRow 消息 (取决于是否声明了一个二进制游标) 返回给前端的每一行.最后,后端发送一条带有标记 "SELECT" 的 CompletedResponse 消息.

EmptyQueryResponse

识别了一个空的查询字串.( 对这个情况的特殊识别是历史原因.)

ErrorResponse

出错了.

ReadyForQuery

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

NoticeResponse

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

前端在等待其他类型的消息时必须准备接收 ErrorResponse 和 NoticeResponse 消息.

实际上,当前端没有等待任何消息时 NoticeResponse 消息也有可能到达, 就是说,后端正常空闲.(具体来说是后端可以被其 postmaster 命令终止运行. 在那种情况下,后端将在关闭联接之前发送一条 NoticeResponse 消息.) 我们建议前端在发出任何命令之前检查这样的异步通知消息.

同样,如果前端发出了任何 listen(l) 命令, 那么它必须在任何时候准备接收 NotificationResponse 消息;见下文.

4.2.3. 函数调用

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

从后端来的可能的响应信息是:

ErrorResponse

发生了一个错误.

FunctionResultResponse

函数调用被执行并且返回一个结果.

FunctionVoidResponse

函数调用被执行并且没有返回结果.

ReadyForQuery

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

NoticeResponse

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

前端在等待其他类型的消息时必须准备接收 ErrorResponse 和 NoticeResponse 消息. 同样,如果前端发出了任何 listen(l) 命令,那么它必须准备在任何时候接收 NotificationResponse 消息;见下文.

4.2.4. 通知响应

如果前端发出了一条 listen(l) 命令, 那么每当为同样名称的通知执行一条 notify(l) 命令, 后端都会发送一条NotificationResponse 消息 (不要与 NoticeResponse 弄混了!).

除了在另外一个后端的消息里以外,在协议里的任何地方(启动后)都允许通知响应. 因此,前端在等待任何消息的时候都必须准备识别 NotificationResponse 消息. 实际上,前端甚至在不能进行查询的时候都要能够处理NotificationResponse 消息.

NotificationResponse

一条 notify(l) 命令为前面执行的 listen(l) 命令的名称被执行. 通知可以在任何时候发送.

有一点值得我们指出来,就是在 listen 和 notify 里面的名称 不必与 SQL 数据库里的关系(表)的名称有任何关系. 通知名只是一个独立选出来的条件名.

4.2.5. 取消正在处理的请求

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

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

除非 CancelRequest 消息包含与联接启动过程中传递给前端的相同的键数据 (PID 和 安全键字), 否则它将被忽略.如果该请求与当前运行着的后端匹配了 PID 和安全键字, postmaster 将给后端发送信号以退出当前查询.

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

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

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

4.2.6. 终止

通常的和优雅的终止过程是前端发送一条 Terminate (终止)消息并且立刻关闭联接. 一旦收到消息,后端马上关闭联接并且退出.

一个欠优雅的的退出可能因为任何一端的软件失效(例如.内核倾倒)产生. 如果前端或后端看到一个意外的联接关闭,它应该清理并退出. 前端可以选择通过 postmaster 重新建立一个新的联接 -- 如果它不想终止自己的处的话.