
引言
PostgreSQL 使用基于消息的协议在前端(客户端)和后端(服务器)之间进行通信。该协议通过 TCP/IP 和 Unix 域套接字支持。
《深度解析 PostgreSQL Protocol v3.0》系列技术贴,将带大家深度了解 PostgreSQL Protocol v3.0(在 PostgreSQL 7.4 及更高版本中实现,有关早期协议版本的描述请参考 PostgreSQL 文档的早期版本,该系列文章不予赘述)相关的消息传输格式和格式码、消息支持的数据类型、消息的格式、协议交互流程、错误消息和通知消息、支持的子协议等,相关的代码解读基于 PostgreSQL 代码仓库的 REL_14_STABLE 分支。
本期是《深度解析 PostgreSQL Protocol v3.0》系列技术贴的第二期文章,在第一期文章中带大家解读了 PostgreSQL Protocol v3.0(一)—概述,本期将为大家分享 PostgreSQL Protocol v3.0(二) — 扩展查询功能的内容。
一、扩展查询介绍
扩展查询(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 消息,返回正常消息处理状态。但是需要

本文是《深度解析PostgreSQL Protocol v3.0》系列第二期,介绍了PostgreSQL扩展查询功能。包括扩展查询协议将简单查询分解多步骤以提高效率,支持流水线操作,还阐述了可能出现的消息格式,以及必要交互流程和带描述准备语句的交互流程。
最低0.47元/天 解锁文章
632

被折叠的 条评论
为什么被折叠?



