type
status
date
slug
summary
tags
category
icon
password
1 协议基础
PostgreSQL 使用基于消息的协议在前端和后端(客户端和服务器)之间进行通信。PostgreSQL通信协议是允许客户端通过TCP/IP协议连接到PostgreSQL服务器,并执行各种操作的一种通信协议。PostgreSQL 通信协议包括两个阶段: startup 阶段和常规 normal 阶段。
- startup 阶段,客户端尝试创建连接,一般正常情况下,客户端会将认证信息发送至服务端,服务端会反馈状态信息,连接成功创建,随后进入 normal 阶段。
- normal 阶段,客户端发送请求至服务端,服务端执行命令并将结果返回给客户端。客户端可以通过两种 "子协议" 来发送请求,分别是 simpel query 和 extened query。使用 simple query 时,客户端发送字符串文本请求,后端收到后立即处理并返回结果;使用 extened query 时,发送请求的过程被分为若干步骤,通常包括Parse、Describe、Bind 、Execute和Sync。
2 消息
2.1 消息格式
客户端和服务端所有通信都通过消息流进行。消息的第一个字节标识消息类型,随后四个字节标识消息内容的长度(该长度包括这四个字节本身),具体的消息内容由消息类型决定。

Tips:客户端创建连接时,发送的第一条消息,即启动(startup)消息格式不同,它没有最开始的消息类型字段,以消息长度开始,随后紧跟协议版本号,然后是键值对形式的连接消息,如用户名、数据库等。

2.2 消息流
2.2.1 Startup——连接认证
Startup 阶段是客户端和服务端创建连接的阶段,包含ssl、认证、链接参数等。这里这几条消息都打包在同一个TCP报文中发送给客户端。消息流如下:

a、TCP三次握手。PostgreSQL在TCP三次握手后进行通信流程。PostgreSQL协议具有用于启动和查询操作的单独阶段。

b、 客户端首先发送startup message到服务器,该消息包括用户名和用户想要连接的数据库的名称,它还标识要使用的特定协议版本。

c、服务器收到后,会进行判断,该用户是否需要密码认证,如果需要则会发送一个Authentication request 来请求密码认证信息。

d、客户端发送加密密码

e、 前端此时发送密码认证成功后,后端会开始发送各种后端参数(即ParameterStatus,包含server_version,client_encoding等)告诉前端当前配置参数,发送ReadyForQuery消息告知客户端一切就绪,至此连接创建成功。

取消请求:在startup阶段,服务端还会给客户端发送一个BackendKeyData消息,该消息包含服务端的进程ID和一个取消码。如果客户端想取消当前正在执行的请求,则可以发送一个CancelRequest消息。取消请求会创建一个新的连接,不再发送
startup
消息,而是发送一个 CancelReqeust
消息,该消息同样没有消息类型字段。
取消请求不保证一定成功,可能服务端接收到取消请求时,当前的查询请求已经结束。取消请求只能在一定程度上加速当前查询结束,如果当前请求被取消,客户端会收到一条错误消息。
2.2.2 Normal——请求查询
连接创建后,通信协议进入Normal阶段,Normal阶段即为发起SQL查询等的阶段。大致流程为:客户端发送查询请求,服务端接收请求,处理请求并将结果返回给客户端。该阶段由两种子协议:Simple Query 与 Extended Query。
a、 简单查询(Simple Query):客户端会发送一个Query消息,服务端处理请求,然后返回查询结果。查询结果包含两个,一是结构(通过RowDescription消息传递),包含列名、类型和长度等;二是数据(通过DataRow消息传递,每一个DataRow消息中包含一行数据)。

一个简单的查询周期是由前端向后端发送一条查询消息来启动的。 该消息包括一个以文本字符串表示的 SQL 命令(或多个命令)。 如下图所示。

然后后端根据查询命令字符串的内容发送一条或多条响应消息,最后发送一条 ReadyForQuery 响应消息。对 SELECT 查询的响应通常包括 RowDescription、零个或多个 DataRow 消息,然后是 Command Complete。复制到或从前端调用特殊协议,所有其他查询类型通常只产生一个 Command Complete 消息,如下图所示。

在简单查询模式下,检索值的格式始终为文本,除非给定命令是来自使用 BINARY 选项声明的游标的 FETCH。在这种情况下,检索到的值是二进制格式。 Row description 消息中给出的格式代码说明正在使用哪种格式,如下图所示。

如果接收到一个完全空的(除空白之外没有其他内容)查询字符串,则响应为 EmptyQueryResponse,后跟 ReadyForQuery;如果发生错误,将发出 ErrorResponse,然后是 ReadyForQuery。查询字符串的所有进一步处理都被 ErrorResponse 中止(即使其中还有更多查询)。请注意,这可能发生在单个查询生成的消息序列的中途。
Tips:一个请求中的多条SQL命令会被当作一个事务执行,如果有命令执行失败,整个事务都会回滚。
b、扩展查询(Extended Query):扩展查询将上述的Simple Query分为多个步骤,每一步都由单独的服务端消息进行确认。相比于Simple Query,使用 Extended Query 时,发送请求的过程被分为若干步骤,准备步骤的结果可以被多次复用以提高效率;另外,还可以获得额外的特性, 比如可以把数据值作为独立的参数提供而不是必须把它们直接插入一个查询字符串。但与 Simple Query 相比,其不允许在一个请求中包含多条 SQL 命令,否则会报语法错误。
Parse : ‘P’ 预处理语句,报文中包含报文长度,命令类型和具体命令的语句等等。在PostgreSQL中叫扩展查询,预处理查询会在查询时候先输入语句进行预处理,语句内部含有一些标识符,代表参数,后面执行的时候再将参数进行填充执行。

CommandComplete :一个SQL命令正常结束。由于一个查询字符串可能包含多个查询(以分号分隔),因此在后端完成对查询字符串的处理之前可能会有多个这样的响应序列。当整个字符串被处理并且后端准备好接受一个新的查询字符串时,ReadyForQuery 被发出,如下图。

① Parse:客户端首先向服务端发送一个 Parse 消息,该消息包括参数化 SQL,参数占位符以及每个参数的类型,还可以指定 Statement 的名字,若不指定名字,即为一个 "未命名" 的 Statement,该 Statement 会在生成下一个 "未命名" Statement 时予以销毁,若指定名字,则必须在下次发送 Parse 消息前将其显式销毁。

② Bind:客户端发送bind消息,该消息携带具体的参数值、参数格式和返回列的格式。

③ Describe:客户端可以发送 Describe 消息获取 Statment 或 Portal 的元信息,即返回结果的列名,类型等信息,这些信息由 RowDescription 消息携带,如果请求获取 Statement 的元信息,还会返回具体的参数信息,由
ParameterDescription
消息携带。
④ Execute:客户端发送 Execute 消息告知服务端执行请求,服务端收到消息后,执行 Bind 阶段创建的 Portal,执行结果通过 DataRow 消息返回给客户端,执行完成后发送 CommandComplete。

⑤ Sync:使用 Extended Query 协议时,一个请求是以 Sync 消息结束,服务端接收到 Sync 消息后,关闭隐式开启的事务并回复 ReadyForQuery 消息。
完整消息流:

总结
本文简要介绍了 PostgreSQL 的通信协议,包括消息格式、消息类型和常见通信过程的消息流。一般通信过程分为两个阶段:
startup
阶段创建连接, normal
阶段发送请求并返回结果。 normal
阶段又包括两种子协议, Simple Query
一次性发送查询请求; Extended Query
分阶段发送请求,利用服务端的 prepared statement 特性,提升反复执行同类请求的效率。PostgreSQL 通信协议中,除本文介绍的 COPY
子协议,还有一些其他的子协议,如主备流复制子协议。psql "postgres://postgres:pgsql@127.0.0.1:5432/postgres?sslmode=disable" -c 'SELECT 1 AS a, 2 AS b;
- Author:叶川-Mask
- URL:https://blog.maskcao.top/article/postgres-xieyi
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!