协议在启动和正常操作过程中有不同的阶段。在启动阶段,前端打开一个到服务器的连接并且认证自身以满足服务器。(这可能涉及到一条或多条消息,取决于使用的认证方法。)如果一切正常,服务器就发送状态信息给前端,并最后进入正常操作。除了最初的启动请求消息之外,协议的这个部分是由服务器驱动的。
在正常操作中,前端发送查询和其他命令到后端,然后后端返回查询结果和其他响应。在少数几种情况(比如NOTIFY)中,后端会发送未被请求的消息,但这个会话中的绝大部分都是由前端请求驱动的。
会话的终止通常是由前端来选择的,但是也可以在某些情况下由后端强制执行。不管在那种情况下,如果后端关闭连接,那么它将在退出之前回滚所有打开的(未完成的)事务。
在正常操作中,SQL命令可以通过两个子协议中的任何一个执行。在“简单查询”协议中,前端只是发送一个文本查询串,然后后端马上分析并执行它。在“扩展查询”协议中,查询的处理被分割为多个步骤:分析、参数值绑定和执行。这样就可以提供灵活性和性能的改进,但代价是额外的复杂性。
正常操作还有用于类似COPY这样的额外子协议。
所有通讯都是通过一个消息流进行的。消息的第一个字节标识消息类型,然后后面跟着的四个字节声明消息剩下部分的长度(这个长度包括长度域自身,但是不包括消息类型字节)。剩下的消息内容由消息类型决定。由于历史原因,客户端发送的最初的消息(启动消息)不包含消息类型字节。
为了避免失去与消息流的同步,服务器和客户端通常都是把整个消息读取到一个缓冲区里(使用字节计数),然后才试图处理其内容。这样在处理内容的过程时如果发现错误,就比较容易恢复。在非常极端的情况下(比如说没有足够的内存缓冲消息),接收端可以使用字节计数来判断它在继续读取消息之前需要跳过多少输入。
反之,服务器和客户端都需要注意决不能发送一条不完整的消息。保证这一点的方法通常是在发送整条消息之前先在一个缓冲区里整理整条消息。如果在发送或者接收一条消息的中间发生了通讯错误,那么唯一合理的响应是放弃连接,因为恢复消息边界同步的希望很小。
在扩展查询协议中,SQL命令的执行是分割成多个步骤的。步骤与步骤之间保存的状态是由两类的对象代表的:预备语句(prepared statements)和门户(portals)。 一个预备语句代表一个文本查询字符串的经过分析、语义解析以及规划之后的结果。一个预备语句不代表它已经可以被执行,因为它可能还缺乏 参数的值。 一个门户代表一个已经可以执行的或者已经被部分执行过的语句,所有缺失的参数值都已经填充到位了(对于SELECT语句,门户等效于一个打开的游标,但我们使用不同的术语是因为游标不能处理非SELECT语句)。
整体执行周期包括一个解析步骤,从文本查询字符串创建一个准备好的语句;一个绑定步骤,根据准备好的语句和任何需要的参数值创建一个门户;以及一个执行步骤来运行门户的查询。对于返回行的查询(SELECT,SHOW等),执行步骤可以指示仅获取有限数量的行,因此可能需要多个执行步骤来完成操作。
后端可以跟踪多个预备语句和门户(但是要注意,这些只存在于一个会话内部,不能在会话之间共享)。现有的预备语句和门户都是用创建它们的时候赋予的名字引用的。 另外,还存在一个“未命名”的预备语句和门户。尽管它们的行为和命名对象大部分相同,但是它们是针对只执行一次然后就抛弃的查询而优化的,而在命名对象上的操作是针对多次使用而优化的。
特定数据类型的数据可以用几种不同的格式中的任意一种来传递。从PostgreSQL 7.4开始,只支持“文本”和“二进制”两种格式,但是协议为未来的扩展提供了手段。任意值要求的格式用一个格式代码声明。客户端可以为每个传输的参数值和查询结果的每个列指定一个格式代码。文本的格式代码是零,二进制的格式代码是一,所有其他的格式代码都保留给将来定义。
文本形式的数值是特定数据类型的输入/输出转换函数生成或接受的任何字符串。在传输形式上,字符串没有末尾空字符;如果前端想把收到的值当作C字符串处理,那么必须自己加上一个(顺便说一下,文本格式不允许嵌入空字符)。
整数的二进制形式采用网络字节序(高位在前)。对于其他数据类型,请参考文档或者源代码获取其二进制形式的信息。请注意,复杂数据类型的二进制形式可能在不同服务器版本之间变化;文本格式通常是最具有移植性的选择。
当前最新的协议版本是 3.2。然而,为了与不支持版本协商的旧服务器版本和中间件向后兼容,libpq 默认仍使用协议版本 3.0。
单个服务器可以支持多个协议版本。初始启动请求消息告诉服务器客户端尝试使用的协议版本。如果客户端请求的主要版本不被服务器支持,则连接将被拒绝(例如,如果客户端请求的协议版本为 4.0,而该版本在撰写时并不存在)。如果客户端请求的次要版本不被服务器支持(例如,客户端请求版本 3.2,但服务器仅支持 3.0),服务器可以拒绝连接,也可以响应一个 NegotiateProtocolVersion 消息,包含其支持的最高次要协议版本。客户端可以选择继续使用指定的协议版本进行连接,或中止连接。
协议协商是在 PostgreSQL 版本 9.3.21 中引入的。早期版本如果客户端请求的次要版本不被服务器支持,则会拒绝连接。
表 54.1 显示了当前支持的协议版本。
表 54.1. 协议版本
| 版本 | 支持者 | 描述 |
|---|---|---|
| 3.2 | PostgreSQL 18 及更高版本 | 当前最新版本。用于查询取消的密钥从 4 字节扩展为可变长度字段。BackendKeyData 消息已更改以适应这一点,CancelRequest 消息被重新定义为具有可变长度有效负载。 |
| 3.1 | - | 保留。版本 3.1 尚未被任何 PostgreSQL 版本使用,但由于流行的 pgbouncer 应用程序的旧版本在协议协商中存在一个错误,导致其错误地声称支持版本 3.1,因此被跳过。 |
| 3.0 | PostgreSQL 7.4 和更高版本 | |
| 2.0 | 支持到 PostgreSQL 13 | 有关详细信息,请参见 PostgreSQL 文档的先前版本 |