原文地址:https://www.highgo.ca/2021/12/10/a-look-inside-postgresqls-extended-query-protocol/
原文作者:Cary Huang
一、简介
几周前,我接到了详细了解 PostgreSQL 的扩展查询协议,并研究其内部机制,以用于依赖此特定功能的项目。
因此在这篇博客中,我将会用我的话来解释扩展协议的工作原理以及它与简单查询的区别。
二、 简单查询
客户端能够针对 PostgreSQL 服务器发起 2 种类型的查询,简单查询或扩展查询。
简单查询,顾名思义,非常简单易懂。当我们启动psql客户端工具连接到 PostgreSQL 服务器时,发送的几乎所有 SQL 命令都被视为简单查询。这包括使用begin和commit封装事务、将多个 SQL 查询包含在一个用分号分隔的大查询中、执行或定义函数等等。
简单查询自动遵循标准查询处理例程,包括以下阶段:
- 解析器
- 分析仪
- 重写器
- 规划师
- 执行者
有关查询处理的详细信息,请参阅此处的博客:https://www.highgo.ca/2019/10/03/trace-query-processing-internals-with-debugger/
客户端和服务器之间的通信也非常简单。在 DML 操作(如 INSERT)的情况下,客户端将查询发送到服务器进行处理,服务器会响应一条CommandComplete消息,例如INSERT 0 1后跟一条ReadyForQuery(IDLE)消息,以指示服务器已完成查询并且现在处于空闲状态。客户端可以发送另一个查询,它遵循相同的模式。
在SELECT查询的情况下,服务器将发送行描述,然后发送满足查询的实际行数据,直到没有更多行要返回。最后,服务器发送一条ReadyForQuery(IDLE)消息,表示服务器已经完成查询,现在处于空闲状态。
三、 扩展查询
扩展查询是客户端完成查询的另一种方式,只是它将标准查询处理分解为不同的步骤,并the client负责确保这些步骤得到正确执行和执行。客户端可以通过发送以下有线协议消息类型来控制这些步骤:
- ’P’ 消息(解析)
Pmessage 接受一个泛化查询字符串,其中的数据值用 , 等占位符替换$1,$2以后可以在 Bind 步骤中用实际值替换。
这个通用查询字符串将通过这些查询处理例程进行解析:Parser-> Analyzer->Rewriter
在成功的 Parse 结束时,prepared statement会产生 a,类似于 SQL 的PREPARE子句
这prepared statement可以是named或unnammed(下一个更多内容)。
这prepared statement只是输入查询的一种表示,还不能执行。
-
’B’ 消息(绑定)
-
B从消息中获取namedor并用用户提供的值替换占位符 ($1, $2)。
-
unnammed prepared statementP
-
将值绑定到 之后prepared statement,我们基本上就有了一个完整的查询,然后它将被输入到planner阶段以产生最优化的查询计划。
-
在成功的计划结束时,portal会产生 a。
-
这portal也可以是named或unnammed
-
门户基本上是一个表示如何执行特定查询的对象
-
-
’E’ 消息(执行)
- E从消息中获取namedor并实际启动以执行查询(或门户)。
- unnamed portalBexecutor
- 生成结果行(如果有)并返回给客户端
-
“S”消息(同步)
- 客户端必须向服务器发送S消息以指示扩展查询的结束。
- 此消息导致服务器结束当前事务并将ReadyForQuery(IDLE)消息发送回客户端。
将一个简单的查询分成多个步骤的目的是什么?使用扩展查询的一个主要好处是它可以节省大量对相同查询结构不必要的解析。相反,我们可以只解析一次公共结构,然后使用不同的值多次绑定和执行。
四、命名和未命名的准备好的声明和门户
在上一节中,我们提到准备好的语句和 protal 可以是扩展查询协议,named也可以是unnamed扩展查询协议期间。它是什么意思,有什么意义?
一般规则是 PostgreSQL 服务器只能保留一个未命名的准备好的语句或门户。包含未命名的准备好的语句或门户的请求将替换现有的。
使用命名的预处理语句或门户,服务器将根据给定的名称分别记住它们,并且客户端可以随时调用它们或通过发送关闭消息专门销毁它们。
因此,客户端可以在一个事务中创建多个准备好的语句,每个都有不同的准备好的语句名称,然后通过给它们不同的门户名称同时为它们绑定值。最终,客户端选择要执行的门户。
更重要的是,命名prepared statement的生命周期持续整个TCP session,除非显式销毁;命名门户仅持续到事务结束或执行或显式销毁时。
但是,如果使用扩展查询,是否会使客户端实现更加复杂?但客户可以选择是否使事情复杂化。
以 libpq 为例,当使用扩展查询时,它需要客户端应用程序提供一个准备好的语句名称来构造P(Parse)消息,但不需要客户端应用程序提供门户名称来构造B(Bind)消息。这意味着使用 libpq,我们可以P使用不同的准备语句名称多次发送消息,但是使用B消息,它会强制使用未命名的门户名称,因此我们总是有一个门户要执行。这避免了在客户端管理多个门户名称的情况。




