引言
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 音讯,返回失常音讯解决状态。然而须要留神,如果在解决 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 音讯数来确定流水线的实现状况。计算命令实现响应数量是不牢靠的,因为一些命令可能会被跳过,因而不会产生实现音讯。
**
三、扩大查问可能呈现的音讯**
四、扩大查问波及音讯的格局
**以下是在扩大查问过程中可能呈现的音讯,上面进行具体介绍。
**1. Parse(F:'P')
Parse 音讯格局如下:**
- Byte1('P')
将音讯标识为 Parse 命令。 - Int32
音讯内容的字节长度,包含本身 4 个字节。 - String
指标筹备语句的名称(空字符串抉择未命名的筹备语句)。 - String
要解析的查问字符串。 - int16
指定的参数数据类型的数目(能够为 0)。请留神,这并不是可能呈现在查问字符串中的参数数量的批示,只是前端心愿为其预先指定数据类型的数量。
而后,对于每一个参数都有以下数据类型 OID: - Int32
指定参数数据类型的 OID。在此处搁置 0 相当于未指定类型。
2. ParseComplete(B:'1')
ParseComplete 音讯格局如下:
- Byte1('1')
将音讯标识为 ParseComplete 音讯。 - Int32(4)
音讯内容的字节长度,包含本身 4 个字节。始终为 4。
3.Bind(F:'B')
Bind 音讯格局如下:
- Byte1('B')
将音讯标识为 Bind 音讯。 - Int32
音讯内容的字节长度,包含本身 4 个字节。 - String
指标门户的名称(空字符串抉择未命名的门户)。 - String
源筹备语句的名称(空字符串抉择未命名的筹备语句)。 - Int16
后续的参数格局代码的数量(上面用C示意)。值能够是:
(1)0,示意没有参数或参数都应用默认格局(text);
(2)1,在这种状况下,指定的格局代码被利用于所有参数;
(3)值等于参数的理论数量。 - Int16[C]
参数格局代码。目前数组每个元素的值必须为 0(text)或 1(binary)。 - Int16
前面的参数值的数目(可能为 0)。值必须与查问所需的参数数量相匹配。
接下来,将为每个参数构建以下字段对:
- Int32
参数值的长度,以字节为单位(此计数不包含其本身)。值能够为零。作为一种非凡状况,-1 示意 NULL 参数值。值为 NULL 状况下,前面没有值字节。 - Byten
参数的值,格局由关联的格局代码批示。n 是上述参数值的长度。在最初一个参数之后,将显示以下两个字段: - Int16
前面的后果列格局代码的数量(上面用 R 示意)。值能够是:
(1)0,示意没有后果列,或者后果列都应该应用默认格局(text);
(2)1,在这种状况下,指定的格局代码被利用于所有后果列(如果有后果列的话);
(3)等于查问的后果列的理论数量。Int16[R]后果列格局代码。目前数组每个元素的值必须为0(text)或1(binary)。
**4.BindComplete(B:'2')
BindComplete 音讯格局如下:** - Byte1('2')
将音讯标识为 BindComplete 音讯。 - Int32(4)
音讯内容的字节长度,包含本身 4 个字节。始终为 4。
5.Describe(F:'D')
Describe 音讯格局如下:
- Byte1('D')
将音讯标识为 Describe 音讯。 - Int32
音讯内容的字节长度,包含本身 4 个字节。 - Byte1
批示 Describe 形容的对象类型。可能的值为:'S',示意形容筹备语句;'P',示意形容门户。 - String
要形容的筹备语句或门户的名称(空字符串抉择未命名的筹备语句或门户)。
形容门户 Portal 的音讯格局如下:
形容筹备语句的音讯格局如下:
6.RowDescription(B:'T')
RowDescription 音讯格局如下:
- Byte1('T')
将音讯标识为行形容。 - Int32
音讯内容的字节长度,包含本身 4 个字节。 - Int16
指定一行中的字段数(能够为 0)。
对于行形容中的每个字段,都有以下 7 局部内容:
- String
字段名称 - Int32
如果字段能够被标识为特定表的列,则值为该表的对象 ID;否则为 0。 - Int16
如果该字段能够标识为特定表的列,则值为该列的属性编号;否则为 0。 - Int32
字段数据类型的对象 ID。 - Int16
数据类型大小(能够参考 pg_type.typlen)。须要留神的是,负值示意可变宽度类型。 - Int32
类型修饰符(能够参考 pg_attribute.atttypmod)。修饰符的含意是特定于数据类型的。 - Int16
字段的格局代码。目前,只能是 0(文本)或 1(二进制)。在从 Describe 语句申请返回的 RowDescription 中,格局代码还未知,并且始终为零。
蓝色背景的局部,是每个列的详细描述。浅灰色背景的局部是可选的,格局是蓝色局部的反复。
7.NoData(B:'n')
NoData音讯格局如下:
- Byte1('n')
将音讯标识为 NoData 音讯。 - Int32(4)
音讯内容的字节长度,包含本身 4 个字节。值始终为 4。
8.ParameterDescription(B:'t')
ParameterDescription 音讯格局如下:
- Byte1('t')
将音讯标识为 ParameterDescription 音讯。 - Int32
音讯内容的字节长度,包含本身 4 个字节。 - Int16
语句应用的参数数量(能够为 0)。
而后,对于每个参数,都有以下内容: - Int32
指定参数数据类型的对象 ID。
如果语句应用的参数数量为 0,则没有后续的参数数据类型的内容。蓝色背景的局部,是每个参数的数据类型对象 ID,是可选的。浅灰色背景的局部是可选的,格局是蓝色局部的反复。
9.Execute(F:'E')
Execute 音讯格局如下:
- Byte1('E')
将音讯标识为 Execute 音讯。 - Int32
音讯内容的字节长度,包含本身 4 个字节。 - String
待执行门户的名称(空字符串抉择未命名的门户)。 - Int32
如果门户蕴含返回行的查问,值为返回的最大行数;否则疏忽该值。0 示意不限度返回行数。
10.DataRow(B:'D')
DataRow 音讯格局如下: - Byte1('D')
将音讯标识为数据行。 - Int32
音讯内容的字节长度,包含本身 4 个字节。 - Int16
后续列值的数量(可能为 0)。
接下来,为每列的值显示以下一对字段:
- Int32
列值的长度,以字节为单位(此计数不包含长度字段本身的 4 个字节)。该字段值能够为零。作为一种非凡状况,-1 示意列值为 NULL。在列值为 NULL 的状况下,前面没有值字节的字段。 - Byten
列的值,格局由关联的格局代码批示。n 是上述字段的长度值。蓝色背景的局部,是每个列值的字节长度和列值,是可选的。浅灰色背景的局部是可选的,格局是蓝色局部的反复。
11.CommandComplete(B:'C')
CommandComplete 音讯格局如下:
- Byte1('C')
将音讯标识为命令实现。 - Int32
音讯内容的字节长度,包含本身 4 个字节。 - String
命令标记。这通常是一个单词,用于标识实现了哪个 SQL 命令。
(1)对于 INSERT 命令,标记是 INSERT oid rows,其中 rows 是插入的行数。如果 rows 为 1 并且指标表具备 oid,则 oid 示意插入行的对象 ID。但 OIDs 零碎列不再受反对;因而 oid 总是 0;
(2)对于 DELETE 命令,标记为 DELETE rows,其中 rows 是删除的行数;
(3)对于 UPDATE 命令,标记是 UPDATE rows,其中 rows 是更新的行数;
(4)对于 SELECT 或 CREATE TABLE AS 命令,标记是 SELECT rows,其中 rows 是检索的行数;
(5)对于 MOVE 命令,标记为 MOVE rows,其中 rows 是游标位置已更改的行数;
(6)对于 FETCH 命令,标记是 FETCH rows,其中 rows 是从游标检索的行数;
(7)对于 COPY 命令,标记是 COPY rows,其中 rows 是复制的行数。(留神:行计数仅呈现在 PostgreSQL 8.2 及更高版本中。)
12.PortalSuspended(B:'s')
PortalSuspended 音讯格局为:
- Byte1('s')
将音讯标识为门户已挂起音讯。请留神,只有当达到 Execute 音讯的行计数限度时,才会呈现此信息。 - Int32(4)
音讯内容的字节长度,包含本身 4 个字节。值始终为 4。
13.EmptyQueryResponse(B:'I')EmptyQueryResponse 音讯格局如下:
- Byte1('I')
将音讯标识为对空查问字符串的响应。(EmptyQueryResponse 音讯将代替 CommandComplete 音讯)。 - Int32(4)
音讯内容的字节长度,包含本身 4 个字节。值始终为 4。
14.Close(F:'C')
Close 音讯格局如下:
- Byte1('C')
将音讯标识为 Close 音讯。 - Int32
音讯内容的字节长度,包含本身 4 个字节。 - Byte1
批示 Close 的对象类型。可能的值为:'S',示意敞开筹备语句;'P',示意敞开门户。 - String
要敞开的筹备语句或门户的名称(空字符串抉择未命名的筹备语句或门户)。
敞开门户 Portal 的音讯格局如下:
敞开筹备语句的音讯格局如下:
15.CloseComplete(B:'3')
CloseComplete 音讯格局如下:
- Byte1('3')
将音讯标识为 CloseComplete 音讯。 - Int32(4)
音讯内容的字节长度,包含本身 4 个字节。始终为 4。
16.Flush(F:'H')
Flush 音讯格局如下:
- Byte1('H')
将音讯标识为 Flush 音讯。 - Int32(4)
音讯内容的字节长度,包含本身 4 个字节。始终为 4。
17.Sync(F:'S')
Sync 音讯格局如下:
- Byte1('S')
将音讯标识为 Sync 音讯。 - Int32(4)
音讯内容的字节长度,包含本身 4 个字节。值始终为4.
18.ErrorResponse(B:'E')
ErrorResponse 音讯格局如下:
- Byte1('E')
将音讯类型标识为谬误。 - Int32
音讯内容的字节长度,包含本身 4 个字节。 - 音讯体
音讯体由一个或多个标识字段组成,后跟一个'\0'字节作为终止符。字段能够按任何程序呈现。对于每个字段,都有以下内容:
(1)Byte1
标识字段类型的代码。如果为零,则这是音讯终止符,前面没有字符串。因为未来可能会增加更多的字段类型,客户端应默默疏忽无奈辨认类型的字段;
(2)String
字段值。
蓝色背景的局部,是音讯体的一个字段类型及其值以及结束符'\0'。一个 ErrorResponse 音讯至多蕴含一个音讯体,浅灰色背景的局部是可选的,格局是蓝色局部的反复。音讯体的最初,有一个'\0'作为音讯的完结。
19.ReadyForQuery(B:'Z')
ReadyForQuery 音讯格局如下:
- Byte1('Z')
将音讯标识为服务器端筹备好接管客户端的查问申请类型。每当服务器为新的查问周期做好筹备时,就会发送 ReadyForQuery。 - Int32(5)
音讯内容的字节长度,包含本身 4 个字节。值总是 5。 Byte1
以后服务器端事务状态指示器。可能的值为:
(1)'I': 处于闲暇状态(不在事务块中);
(2)'T': 在事务块中;
(3)'E': 在失败的事务块中(查问将被回绝,直到事务块完结)。五、扩大查问交互流程
扩大查问协定将简略查问协定合成为多个步骤,能够多次重复应用筹备步骤的后果,以此提高效率。此外,扩大查问还提供了其余性能,例如能够将数据值作为独自的参数提供,而不用将其直接插入查问字符串中。
扩大查问交互流程,次要有两种场景:
(1)扩大查问的必要交互流程;
(2)扩大查问带形容筹备语句的交互流程。
1.扩大查问的必要交互流程
扩大查问必要的交互流程次要是 Parse、Bind、portal-Describe、Execute、Sync 等。扩大查问的次要流程如下:(1)先发送蕴含单条 SQL 语句的 Parse 音讯,创立筹备语句和 Portal,不便后续步骤反复利用;
(2)通过 Bind 音讯提供筹备语句中参数占位符须要的参数值;
(3)通过 Describe 音讯,获取 Portal 执行将要返回的数据行的信息。该步骤是可选的;
(4)发送 Execute 音讯,执行以后的 Portal,并返回执行后果的数据;
(5)执行完上述流程,发送 Sync 音讯提交数据;
(6)能够持续反复执行步骤 (2)~(5)的 Bind、Describe、Execute、Sync 流程,进行数据的读写。具体的交互音讯和交互流程,如下图所示:
2.扩大查问带形容筹备语句的交互流程
扩大查问带形容筹备语句的交互流程次要是 Parse、筹备语句 Describe、Bind、Execute、Sync 等,次要是在 Parse 和 Bind 之间,发送筹备语句的形容申请音讯。次要流程如下:
(1) 先发送蕴含单条 SQL 语句的 Parse 音讯,创立筹备语句和 Portal,不便后续步骤反复利用;
(2)发送筹备语句 Describe,服务器端响应 ParameterDescription 音讯和 RowDescription 音讯。ParameterDescription 音讯形容筹备语句须要的参数信息,RowDescription 音讯形容返回数据行的列布局信息;(3)通过 Bind 音讯提供筹备语句中参数占位符须要的参数值;
(4)通过 Describe 音讯,获取 Portal 执行将要返回的数据行的信息。该步骤是可选的;
(5)发送 Execute 音讯,执行以后的 Portal,并返回执行后果的数据;
(6)执行完上述流程,发送 Sync 音讯提交数据;
(7)能够持续反复执行步骤(3)~(6)的 Bind、portal-Describe、Execute、Sync 流程,进行数据的读写。
具体的交互音讯和交互流程,如下图所示: