暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

深度解析 PostgreSQL Protocol v3.0 — 综述(上)

原创 KaiwuDB 2024-02-26
437

系列目录

深度解析 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 分支。

一、扩展查询介绍

扩展查询(Extended Query)协议将简单查询协议分解为多个步骤,为提高效率,可多次重复使用准备(Prepare)步骤的结果。此外,扩展查询协议还提供了其他功能,例如可以将数据值作为单独的参数提供,而不必将它们直接插入到查询字符串中。

在扩展查询协议中,客户端首先发送一条 Parse 消息,其中包含文本查询字符串、可选的参数占位符的数据类型信息以及目标准备语句对象的名称(目标准备语句对象名称为空字符串,则选择未命名的准备语句)。响应为 ParseComplete 或 ErrorResponse 消息。参数数据类型可以由 OID 指定;如果没有指定参数数据类型,解析器将尝试以与无类型的文本字符串常量相同的方式推断数据类型。

Parse 过程需要注意:

(1)参数的数据类型可以不指定,此时设置参数数据类型的 OID 为 0,或者使参数数据类型 OID 的数组比查询字符串中使用的参数符号的数量($n)短。另一种特殊情况是参数的数据类型可以指定为 void(即 void 伪类型的 OID)。

这意味着允许实际上是输出 OUT 使用的参数符号,作为函数的入参使用。通常情况下,没有可以使用 void 参数的上下文,但如果函数的参数列表中出现了这样的参数符号,则实际上会忽略它。例如,如果将 $3 和 $4 指定为具有 void 类型,则诸如 foo($1, $2, $3, $4) 之类的函数调用可以匹配具有两个 IN 和两个 OUT 参数的函数。

**(2)**Parse 消息中包含的查询字符串不能包含多个 SQL 语句,否则报告语法错误。这种限制在简单查询协议中不存在,但在扩展查询协议中确实存在,因为允许准备语句或门户包含多个 SQL 命令会使协议过度复杂化。

如果成功创建命名的准备语句对象,除非明确销毁它,否则它将持续到当前会话结束。未命名的准备语句只持续到处理下一个指定未命名语句为目标的 Parse 语句为止。

需要特别注意的是,简单查询消息 Query 也会销毁未命名的准备语句。命名的准备语句必须显式关闭,然后才能被另一个 Parse 消息重新定义,但这对于未命名语句来说不是必需的。还可以使用 PREPARE 和 EXECUTE 在 SQL 命令级别创建和访问命名的准备语句。

一旦准备语句存在,就可以使用 Bind 消息为执行做好准备。Bind 消息提供源准备语句的名称(空字符串表示未命名的准备语句)、目标门户的名称(空字符串表示未命名的门户)以及准备语句中每个参数占位符的值。提供的参数集必须与准备语句所需的参数集匹配。

如果在 Parse 消息中声明了任何 void 参数,在 Bind 消息中为它们传递 NULL 值。Bind 还指定用于查询返回数据的格式;返回数据格式可以整体指定,也可以按列指定。Bind 消息的响应为 BindComplete 或 ErrorResponse 消息。

**Bind 过程需要注意:**文本和二进制输出之间的选择取决于 Bind 中给出的格式代码,而不考虑所涉及的 SQL 命令。当使用扩展查询协议时,游标(CURSOR)声明中的 BINARY 属性无关紧要。

查询的计划生成通常在处理 Bind 消息时进行。如果准备语句没有参数,或者被重复执行,服务器可能会缓存创建的计划,并在同一准备语句的后续 Bind 消息中重用它。但是,只有当服务器发现可以创建的通用计划的效率比依赖于提供的特定参数值的计划高得多时,它才会这样做。但是就扩展协议而言,这个过程是透明的。

如果成功创建命名门户对象,除非明确销毁它,否则它将持续到当前事务结束。在事务结束时,或在发出指定未命名门户为目标的下一个 Bind 语句时,将销毁未命名门户。需要特别注意的,简单查询的消息 Query 也会销毁未命名的门户。在可以通过另一个 Bind 消息重新定义命名门户之前,必须显式关闭命名门户,但这对于未命名门户不是必需的。命名门户也可以使用 DECLARE CURSOR 和 FETCH 在 SQL 命令级别创建和访问。

一旦门户存在,就可以使用 Execute 消息执行它。Execute 消息指定门户名称(空字符串表示未命名的门户)和最大结果行计数(0 表示“获取所有行”)。结果行计数仅对包含返回行数据集的命令的门户有意义;在其他情况下,命令始终执行直至完成,并且忽略行计数。对 Execute 消息的可能响应与通过简单查询协议发出的 Query 消息的响应相同,但 Execute 不会导致 Server 端发出 ReadyForQuery 和 RowDescription 消息。

如果 Execute 在门户执行完成之前终止(由于达到非 0 结果行计数),服务器端将发送 PortalSuspended 消息。PortalSuspended 消息的出现告诉客户端,应针对同一门户发出另一个 Execute 消息以完成操作。在门户执行完成之前,不会发送指示源 SQL 命令执行完成的 CommandComplete 消息。因此,Execute 阶段总是由以下消息之一的出现而终止:CommandComplete、EmptyQueryResponse(如果门户是从空查询字符串创建的)、ErrorResponse 或 PortalSuspended。

在完成每个扩展查询消息系列时,客户端应发出一条 Sync 消息。如果当前事务不在 BEGIN/COMMIT 显式事务块内,则无参数 Sync 消息会导致服务器端关闭当前事务(这里的“关闭”表示如果没有错误则提交,如果错误则回滚)。然后发出 ReadyForQuery 响应。Sync 消息的目的是为错误恢复提供新的同步点。

当在处理任何扩展查询消息时检测到错误,服务器端会发出 ErrorResponse 消息,然后读取并丢弃消息,直至收到 Sync 消息,然后发出 ReadyForQuery 消息,返回正常消息处理状态。但是需要注意,如果在处理 Sync 消息时检测到错误,则不会跳过该消息处理,这确保每个 Sync 消息都有且只有一个 ReadyForQuery 响应消息发送到客户端。

另外,Sync 消息不会导致用 BEGIN 打开的事务块关闭。ReadyForQuery 消息包含事务状态信息,因此可以检测到这种情况。

除了上述必备的基本操作之外,还有几个可选操作可用于扩展查询协议:****

Describe 消息(门户描述变体)指定现有门户的名称(或空字符串指示未命名门户)。响应是一条 RowDescription 消息,描述门户执行将返回的数据行;如果门户不包含将返回数据行的查询,则返回 NoData 消息;如果指定的门户不存在,则返回 ErrorResponse 消息。

Describe 消息(语句描述变体)指定现有准备语句的名称(或空字符串指示未命名准备语句)。响应是一条 ParameterDescription 消息,描述指定语句所需的参数,紧跟着是一条 RowDescription 消息,描述最终执行语句时将返回的数据行(如果该语句不返回数据行,则返回 NoData 消息)。

如果指定的准备语句不存在,则会响应 ErrorResponse 消息。需要注意的是,由于尚未收到 Bind 消息,因此服务器端还不知道用于数据返回的列格式;在这种情况下,RowDescription 消息中的格式代码字段将为 0。

在大多数情况下,客户端应该在发送 Execute 消息之前发送一个门户或语句 Describe 变体消息,以确保它知道如何解释将返回的结果。

Close 消息关闭现有的准备语句或门户,并释放资源。对不存在的语句或门户名称发出 Close 消息不会引起错误。Close 消息的响应通常是 CloseComplete,但如果在释放资源时遇到一些困难,则可能是 ErrorResponse 消息。需要注意的是,关闭准备语句会隐式关闭由该语句构建的所有打开的门户。

Flush 消息不会导致生成任何特定的输出,但会强制服务器端发送其输出缓冲区中已经存在的任何数据。如果客户端希望在发出更多命令之前检查该命令的结果,则必须在除 Sync 之外的任何扩展查询命令之后发送 Flush。如果没有 Flush 消息,服务器端返回的消息将被组合成尽可能少的数据包数量,以最大限度地减少网络开销。

因此,简单查询消息 Query 大致相当于 Parse、Bind、portal-Description、Execute、Close、Sync 系列命令,使用未命名的准备语句和门户的对象,不使用参数。

不同之处在于:

(1)简单查询的 Query 消息将接受查询字符串中的多个 SQL 语句,自动为每个语句连续执行 bind/describe/execute 命令序列;

(2)简单查询的 Query 消息的响应不会是 ParseComplete、BindComplete、CloseComplete 和 NoData 消息。

二、扩展查询支持流水线操作

扩展查询协议允许流水线(Pipelining)操作,这意味着可以发送一系列查询语句,而无需等待较早的查询完成。这减少了完成一系列给定操作所需的网络往返次数。但是,如果其中一个步骤失败,用户必须仔细考虑处理错误所需的行为,因为后续的查询已经在向服务器发送。

处理这一问题的一种方法是使整个查询系列成为一个单独的事务,即将查询序列封装在 BEGIN…COMMIT 中。然而,如果希望某些命令独立于其他命令进行单独提交,该方法并没有帮助。

扩展查询协议提供了另一种管理此问题的方法,即省略在依赖步骤之间发送同步消息。由于在发生错误后,服务器端将跳过命令消息,直至找到 Sync 消息,这允许在前一个命令失败时自动跳过流水线中的后一个命令,而客户端不必使用 BEGIN 和 COMMIT 显式管理命令序列。流水线中可独立提交的段可以通过 Sync 消息来分隔。

如果客户端没有发出显式的 BEGIN,那么如果前面的步骤成功,则每个 Sync 通常会导致事务隐式的 COMMIT,如果失败,则会引起事务隐式的 ROLLBACK。但是,有一些 DDL 命令(如 CREATE DATABASE)无法在事务块内执行。如果一个这种 DDL 命令在流水线中执行,除非它是流水线中的第一个命令,否则都将失败。

此外,这种 DDL 命令一旦执行成功,它将强制立即提交以保持数据库一致性。因此,在其中一个命令之后立即发送 Sync 消息除了使用 ReadyForQuery 进行响应之外没有任何效果。使用此方法时,必须通过 ReadyForQuery 消息计数并等待达到发送的 Sync 消息数来确定流水线的完成情况。计算命令完成响应数量是不可靠的,因为一些命令可能会被跳过,因此不会产生完成消息。

下一篇:深度解析 PostgreSQL Protocol v3.0 — 综述(下)

最后修改时间:2024-02-26 16:19:06
文章转载自KaiwuDB,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论