Table of Contents
PostgreSQL使用一种基于消息的协议在前端和后端(客户端与服务器)之间进行通信。该协议既支持TCP/IP,也支持 Unix 域套接字。端口号 5432 已在 IANA 注册为支持该协议的服务器的惯用 TCP 端口号,但实际上任何非特权端口号都可以使用。
本文描述协议 3.0 版本,自 PostgreSQL 7.4 起实现。关于更早协议版本的说明,请参阅之前发布的 PostgreSQL 文档。一台服务器可以支持多个协议版本。初始启动请求消息会告知服务器客户端正尝试使用哪个协议版本。如果客户端请求的主版本号不被服务器支持,则连接会被拒绝(例如,如果客户端请求协议版本 4.0,而在本文编写时该版本并不存在,就会出现这种情况)。如果客户端请求的次版本号不被服务器支持(例如客户端请求 3.1,而服务器只支持 3.0),服务器可以拒绝连接,也可以返回一条 NegotiateProtocolVersion 消息,其中包含它所支持的最高次协议版本。客户端随后可以选择使用指定的协议版本继续连接,或者中止连接。
为了高效地为多个客户端提供服务,服务器会为每个客户端启动一个新的“后端”进程。在当前实现中,一旦检测到传入连接,就会立刻创建新的子进程。不过,这一点对协议而言是透明的。就协议而言,术语“后端”和“服务器”可以互换;同样,“前端”和“客户端”也可以互换。
协议分为启动和正常操作两个阶段。在启动阶段,前端打开到服务器的连接,并完成服务器所要求的认证。(这可能只涉及一条消息,也可能因所用认证方法不同而需要多条消息。)如果一切顺利,服务器随后会向前端发送状态信息,并最终进入正常操作。除最初的启动请求消息外,协议的这一部分由服务器驱动。
在正常操作中,前端向后端发送查询及其他命令,后端则返回查询结果和其他响应。少数情况下(例如 NOTIFY),后端会发送未请求的消息,但会话中的绝大多数交互仍由前端请求驱动。
会话通常由前端选择终止,但在某些情况下也可能由后端强制终止。无论哪种情况,后端在关闭连接前都会回滚所有打开的(未完成的)事务。
在正常操作中,SQL 命令可以通过两种子协议之一执行。在“简单查询”协议中,前端只需发送文本形式的查询字符串,后端会立即对其进行解析并执行。在“扩展查询”协议中,查询处理被拆分为多个步骤:解析、参数值绑定以及执行。这带来了更高的灵活性和性能收益,但代价是额外的复杂性。
正常操作还包含用于COPY等特殊操作的额外子协议。
所有通信都通过消息流进行。消息的第一个字节标识消息类型,接下来的四个字节给出消息其余部分的长度(该长度计数包含自身,但不包括消息类型字节)。消息剩余内容由消息类型决定。由于历史原因,客户端发送的第一条消息(启动消息)没有开头的消息类型字节。
为了避免与消息流失去同步,服务器和客户端通常都会先根据字节计数把整条消息读入缓冲区,然后再处理其内容。这样一来,如果在处理内容时检测到错误,就比较容易恢复。在极端情况下(例如没有足够内存缓冲整条消息),接收方也可以利用字节计数判断在恢复读取消息之前需要跳过多少输入。
反过来,服务器和客户端也必须注意绝不能发送不完整的消息。通常的做法是在开始发送之前,先在缓冲区中整理好整条消息。如果在发送或接收消息的途中发生通信故障,唯一合理的做法就是放弃连接,因为几乎不可能重新恢复消息边界的同步。
在扩展查询协议中,SQL 命令的执行被拆分为多个步骤。各步骤之间保留的状态由两类对象表示:预备语句和portal。预备语句表示对文本查询字符串完成解析和语义分析后的结果。预备语句本身还不能直接执行,因为它可能缺少特定的参数值。portal 表示一条已经可以执行、或已经部分执行过的语句,其中所有缺失的参数值都已补齐。(对于SELECT语句,portal 等价于一个打开的游标;但由于游标不能处理非SELECT语句,这里采用不同术语。)
整个执行周期包括一个解析步骤,它从文本查询字符串创建预备语句; 一个绑定步骤,它根据预备语句和所需参数值创建 portal; 以及一个执行步骤,用于执行 portal 中的查询。对于返回行的查询 (SELECT、SHOW等),可以要求执行步骤只取回 有限数量的行,因此可能需要多次执行步骤才能完成整个操作。
后端可以跟踪多个预备语句和 portal(但请注意,它们只存在于单个会话内,绝不会在会话之间共享)。已有的预备语句和 portal 都通过创建时赋予的名称来引用。此外,还存在一个“未命名”的预备语句和 portal。虽然它们的行为与有名对象大体相同,但对未命名对象的操作是为“只执行一次然后丢弃”的场景优化的,而对有名对象的操作则是基于会被多次使用的预期进行优化的。
某一特定数据类型的数据可以使用多种不同的格式之一进行传输。自 PostgreSQL 7.4 起,当前只支持“文本”和“二进制”两种格式,但协议为未来扩展留出了空间。任意值所需的格式由格式代码指定。客户端可以为每个传输的参数值以及查询结果的每一列指定格式代码。文本格式的代码为零,二进制格式的代码为一,其他格式代码则保留供将来定义。
文本形式的数值是特定数据类型的输入/输出转换函数生成或接受的任何字符串。在传输形式上,字符串没有末尾空字符;如果前端要想把收到的值当作C字符串处理,那么必须自己加上一个(顺便说一下,文本格式不允许嵌入空字符)。
整数的二进制表示采用网络字节序(最高有效字节在前)。至于其他数据类型,请查阅文档或源代码了解其二进制表示形式。要注意,复杂数据类型的二进制表示可能会在不同服务器版本之间发生变化;文本格式通常是可移植性更好的选择。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。