系列目录
深度解析 PostgreSQL Protocol v3.0 — 综述(上)
深度解析 PostgreSQL Protocol v3.0 — 综述(下)
深度解析 PostgreSQL Protocol v3.0 — 扩展查询(上)
深度解析 PostgreSQL Protocol v3.0 — 扩展查询(下)
引言
PostgreSQL 使用基于消息的协议在前端(客户端)和后端(服务器)之间进行通信。该协议通过 TCP/IP 和 Unix 域套接字支持。
《深度解析 PostgreSQL Protocol v3.0》系列技术贴,将带大家深度了解 PostgreSQL Protocol v3.0(在 PostgreSQL 7.4 及更高版本中实现,有关早期协议版本的描述请参考 PostgreSQL 文档的早期版本,该系列文章不予赘述)相关的消息传输格式和格式码、消息支持的数据类型、消息的格式、协议交互流程、错误消息和通知消息、支持的子协议等,相关的代码解读基于 PostgreSQL 代码仓库的 REL_14_STABLE 分支。
一、消息的交互流程
PostgreSQL Protocol v3.0 的交互流程主要包括以下几种流程:
1. 启动流程
要开始会话,客户端将打开与服务器的连接并发送启动消息 StartupMessage。启动消息包括用户的名称、用户想要连接到的数据库的名称和要使用的特定协议版本(启动消息可以包括运行时参数的其他设置,但是这些参数都是可选的)。
接着,服务器使用这些信息及其配置文件(如 pg_hba.conf)的内容来确定连接是否暂时可接受,以及需要什么附加身份验证(如果有的话)。
然后,服务器发送适当的身份验证请求消息,客户端必须用适当的身份认证响应消息(如密码)回复该消息。
对于除 GSSAPI、SSPI 和 SASL 之外的所有身份验证方法,最多只有一个请求和一个响应。在某些方法中,客户端不需要响应,因此不会发生身份验证请求。对于 GSSAPI、SSPI 和 SASL,可能需要多次交换数据包才能完成身份验证。
2. 简单查询流程
一个简单查询的周期由客户端端向服务器端发送查询消息来启动。该消息包括一个以文本字符串表示的 SQL 命令。然后,服务器根据查询命令字符串的内容进行执行,执行完成发送一条或多条响应消息,最后发送 ReadyForQuery 响应消息。
ReadyForQuery 通知客户端,可以安全地发送新命令。(客户端实际上不需要在发出另一个命令之前等待 ReadyForQuery,但客户端必须负责弄清楚如果前一个命令失败,而已经发出的后一个命令成功,会发生什么情况。因此,建议的做法是客户端接收到 ReadyForQuery 消息之后再发送新命令。)
简单查询的交互流程中,也会出现一些异常情况,会得到异常的响应。例如,查询 SQL 为空字符串,则响应为 EmptyQueryResponse,后跟 ReadyForQuery。发生错误时,发出 ErrorResponse,然后发出 ReadyForQuery。ErrorResponse 会中止对查询字符串的所有进一步处理。
3. 扩展查询
扩展查询协议将上述简单查询协议分解为多个步骤。为了提高效率,可以多次重复使用 Prepare 步骤的结果。
此外,还提供了其他功能,例如可以将数据值作为单独的参数提供,而不必将它们直接插入到查询字符串中。扩展查询一般需要经过 Parse, Bind 和 Execute 步骤,中间有一些可选步骤如 Describe,Close 和 Flush。
4. Pipelining
扩展查询协议的使用允许流水线,这意味着发送一系列查询而无需等待较早的查询完成。流水线减少了完成给定系列操作所需的网络往返次数。
但是,如果其中一个步骤失败,用户必须仔细考虑所需的处理,因为稍后的查询已经在发送到服务器。
5. 函数调用(Function Call)流程
函数调用(Function Call)子协议允许客户端请求直接调用数据库的 pg_proc 系统目录中存在的任何函数。客户端必须具有函数的执行权限。
函数调用子协议是一个较早版本的遗留功能,在新代码/新版本中最好避免使用。类似的结果可以通过设置执行 SELECT function($1, …)的准备语句的值来实现。然后可以用 Bind/Execute 代替函数调用周期。
函数调用周期由客户端向端发送 FunctionCall 消息来启动。服务端根据函数调用的结果发送一条或多条响应消息,最后发送 ReadyForQuery 响应消息。ReadyForQuery 通知客户端它可以安全地发送新的查询或函数调用。
6. 取消执行中的请求流程
在处理查询期间,客户端可能会请求取消查询。出于实现效率的原因,取消请求不会直接通过正在执行查询的连接发送到服务端:不希望服务端在查询处理过程中不断检查来自客户端的新输入。取消请求应该是相对较少的,所以我们让取消流程稍微麻烦一些,以避免在正常情况下发生错误。
要发出取消请求,客户端应该打开到服务器的新连接并发送一条 CancelRequest 消息,而不是通常通过新连接发送的 StartupMessage 消息。服务器将处理此请求,然后关闭连接。出于安全原因,不直接回复取消请求消息。
7. 结束流程
正常、友好的终止过程是客户端发送 Terminate 终止消息并立即关闭连接。服务端收到此 Terminate 消息后,关闭连接并终止。
在极少数情况下(如管理员通过命令关闭数据库),服务器端可能会在没有任何客户端请求的情况下断开连接。在这种情况下,服务器端将尝试在关闭连接之前发送错误或通知消息,给出断开连接的原因。
8. COPY 操作
COPY 命令允许客户端与服务器之间进行高速批量数据传输。COPY IN 和 COPY OUT 操作都会将连接切换到不同的子协议中,该子协议将持续到操作完成。
COPY IN 是将数据从客户端传输到服务器端,COPY OUT 是将数据从服务器端传输到客户端。还有另一种与 COPY 相关的模式,称为“双向复制”,它允许客户端与服务器之间的双向高速批量数据传输。
9. 异步操作
有几种情况下,服务器端将向客户端发送客户端命令没有特别请求的消息。客户端必须随时准备好处理这些消息,即使这些消息不是为了响应查询请求。
因此,客户端在开始读取查询响应之前,至少应该检查这些情况。服务端异步发送给客户端的消息主要有两种类型:NoticeResponse 消息和 ParameterStatus 消息。
二、错误消息和通知消息
错误消息 ErrorResponse 和通知消息 NoticeResponse,通常是在服务器端处理失败或者发生异常场景时,通知客户端执行失败或者服务器端异常原因的消息。
错误消息和通知消息中可能出现的每个字段类型都有一个单字节标识,并且任何给定的字段类型在每条消息中最多出现一次。错误消息和通知消息中可能出现的字段及其含义如下表所示。
客户端负责格式化显示错误消息和通知消息的信息以满足其需要。客户端应该根据需要进行换行等,错误消息字段中出现的换行符应视为段落分隔符,而不是换行符。
三、其他子协议简介
1. 流复制协议(Streaming Replication Protocol)和逻辑流复制协议(Logical Streaming Replication Protocol)
要启动流复制,客户端在启动消息中发送 replication 参数。replication 参数为布尔值,值为 true(或 on,yes,1)告诉服务器端进入物理复制 walsender 模式,其中可以发出一组复制命令,而不是 SQL 语句。
将 database 作为 replication 参数的值传递,指示服务器端进入逻辑复制 walsender 模式,连接到 dbname 参数中指定的数据库。在逻辑复制 walsender 模式下,可以发出复制命令以及正常的 SQL 命令。在物理复制或逻辑复制 walsender 模式中,只能使用简单的查询协议。
这两种协议,主要应用于主备服务器数据同步的场景。流复制也叫物理复制,是基于对文件块的流复制,逻辑复制是基于对数据元组按照一定格式进行复制。
2. 加密协议
(1)SSL 会话加密
如果 PostgreSQL 的构建选项使用了 SSL,那么客户端和服务器端通信可以使用 SSL 加密。SSL 会话加密在攻击者可能能够捕获会话流量的环境中提供了通信安全性。
(2)GSSAPI 会话加密
如果 PostgreSQL 的构建选项是使用了 GSSAPI,则可以使用 GSSAPI 加密客户端和服务器端的通信流量。这在攻击者可能能够捕获会话流量的环境中提供了通信安全性。




