PostgreSQL 通信协议

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 我们在使用数据库服务时,通常需要使用客户端连接数据库服务端,以 PostgreSQL 为例,常用的客户端有自带的 psql,JAVA 应用的数据库驱动 JDBC,可视化工具 PgAdmin 等,这些客户端都需要遵守 PostgreSQL 的通信协议才能与之 "交流"。所谓协议,可以理解为一套信息交互规则或者规范,最为我们熟知的莫过于 TCP/IP 协议和 HTTP 协议。 ![image.p

我们在使用数据库服务时,通常需要使用客户端连接数据库服务端,以 PostgreSQL 为例,常用的客户端有自带的 psql,JAVA 应用的数据库驱动 JDBC,可视化工具 PgAdmin 等,这些客户端都需要遵守 PostgreSQL 的通信协议才能与之 "交流"。所谓协议,可以理解为一套信息交互规则或者规范,最为我们熟知的莫过于 TCP/IP 协议和 HTTP 协议。

image.png

PostgreSQL 在 TCP/IP 协议之上实现了一套基于消息的通信协议,同时,为避免客户端和服务端在同一台机器时的网络通信代价,也支持在 Unix 域套接字上使用该协议。PostgreSQL 至今共实现了三个版本的通信协议,现在普遍使用的是从 7.4 版本开始使用的 3.0 版本,其他版本的协议依然支持。一个 PostgreSQL 数据库实例同时支持所有版本的协议,具体使用那个版本取决于客户端的选择,无论选择哪个版本,客户端和服务端需要匹配,否则可能无法正常 "交流"。本文介绍 PostgreSQL 3.0 版本的通信协议。

PostgreSQL 是多进程架构,守护进程 Postmaster 为每个连接分配一个后台进程(backend),后台进程的分配是在协议处理之前进行的,每个后台进程自行负责协议的处理。在 PostgreSQL 源码或者文档中,通常认为 'backend' 和 'server' 是等价的,表示服务端;同样,'frontend' 和 'client' 是等价的,表示客户端。

协议基础

PostgreSQL 通信协议包括两个阶段: startup 阶段和常规 normal 阶段。 startup 阶段,客户端尝试创建连接并发送授权信息,如果一切正常,服务端会反馈状态信息,连接成功创建,随后进入 normal 阶段。 normal 阶段,客户端发送请求至服务端,服务端执行命令并将结果返回给客户端。客户端请求结束后,可以主动发送消息断开连接。

normal 阶段,客户端可以通过两种 "子协议" 来发送请求,分别是 simpel query 和 extened query。使用 simple query 时,客户端发送字符串文本请求,后端收到后立即处理并返回结果;使用 extened query 时,发送请求的过程被分为若干步骤,通常包括 Parse,Bind 和 Execute。

本节介绍通信协议的基础,包括消息格式和基本的消息流, normal 阶段的两种 "子协议" 在下一节详细介绍。

消息

消息格式

客户端和服务端所有通信都通过消息流进行。消息的第一个字节标识消息类型,随后四个字节标识消息内容的长度(该长度包括这四个字节本身),具体的消息内容由消息类型决定。

image.png

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

image.png

startup 消息的处理流程可以参考 ProcessStartupPacket

消息类型

PostgreSQL 目前支持如下客户端消息类型:

case 'Q':            /* simple query */
case 'P':            /* parse */
case 'B':            /* bind */
case 'E':            /* execute */
case 'F':            /* fastpath function call */
case 'C':            /* close */
case 'D':            /* describe */
case 'H':            /* flush */
case 'S':            /* sync */
case 'X':
case EOF:
case 'd':            /* copy data */
case 'c':            /* copy done */
case 'f':            /* copy fail */

服务端收到如上消息的处理流程可以参考 PostgresMain。服务端发送给客户端的消息有如下类型(不完全):

case 'C':        /* command complete */
case 'E':        /* error return */
case 'Z':        /* backend is ready for new query */
case 'I':        /* empty query */
case '1':        /* Parse Complete */
case '2':        /* Bind Complete */
case '3':        /* Close Complete */
case 'S':        /* parameter status */
case 'K':        /* secret key data from the backend */
case 'T':        /* Row Description */
case 'n':        /* No Data */
case 't':        /* Parameter Description */
case 'D':        /* Data Row */
case 'G':        /* Start Copy In */
case 'H':        /* Start Copy Out */
case 'W':        /* Start Copy Both */
case 'd':        /* Copy Data */
case 'c':        /* Copy Done */
case 'R':        /* Authentication Request */

客户端处理如上服务端消息的流程可以参考 PostgreSQL libqp 的实现 pqParseInput3

消息流

Startup

startup 阶段是客户端和服务端创建连接的阶段,消息流如下:

image.png

客户端首先发送 startup 消息至服务端,服务端判断是否需要授权信息,如若需要,则发送 AuthenticationRequest ,客户端随后发送密码至服务端,权限验证之后,服务端给客户端发送一些参数信息,即 ParameterStatus ,包括 server_version , client_encoding 和 DateStyle 等。最后,服务端发送一个 ReadyForQuery 消息,告知客户端一切就绪,可以发送请求了。至此,连接创建成功。

取消请求

startup 阶段,服务端还会给客户端发送一个 BackendKeyData 消息,该消息中包含服务端的进程 ID 和一个取消码(MyCancelKey)。如果客户端想取消当前正在执行的请求,则可以发送一个 CancelRequset 消息,该消息中包括 startup 阶段服务端提供的进程 ID 和取消码。

取消请求并不是通过当前正在处理请求的连接发送的,而是会创建一个新的连接,创建该连接发送的消息与之前创建连接的消息不同,不再发送 startup 消息,而是发送一个 CancelReqeust 消息,该消息同样没有消息类型字段。

image.png

取消请求不保证一定成功,可能服务端接收到取消请求时,当前的查询请求已经结束。取消请求只能在一定程度上加速当前查询结束,如果当前请求被取消,客户端会收到一条错误消息。

发送请求

连接创建之后,通信协议进入 normal 阶段,该阶段的大体流程是:客户端发送查询请求,服务端接收请求、处理请求并将结果返回给客户端。上文提到,该阶段有两种 "子协议",本节分别介绍这两种 "子协议" 的消息流。

Simple Query

客户端通过 Query 消息发送一个文本命令给服务端,服务端处理请求,回复查询结果。查询结果通常包括两部分内容:结构和数据。结构通过 RowDescription 消息传递,包括列名、类型 OID 和长度等;数据通过 DataRow 消息传递,每个 DataRow 消息中包含一行数据。

image.png

每个命令的结果发送完成之后,服务端会发送一条 CommandComplete 消息,表示当前命令执行完成。客户端的一条查询请求可能包含多条 SQL 命令,每个 SQL 命令执行完都会回复一条 CommandComplete 消息,查询请求执行结束后会回复一条 ReadyForQuery 消息,告知客户端可以发送新的请求。消息流如下:

image.png

注意,一个请求中的多条 SQL 命令会被当做一个事务来执行,如果有命令执行失败,整个事务都会回滚。用户可以在请求中显式添加 BEGIN 和 COMMIT ,将一个请求划分为多个事务,避免事务全部回滚。显式添加事务控制语句的方式无法避免请求有语法错误的情况,如果请求有语法错误,整个请求都不会被执行。

ReadyForQuery 消息会反馈当前事务的执行状态,客户端可以根据事务状态做相应的处理,目前有如下三种事务状态:

'I';            /* idle --- not in transaction */
'T';            /* in transaction */
'E';            /* in failed transaction */

Extended Query

Extended Query 协议将以上 Simple Query 的处理流程分为若干步骤,每一步都由单独的服务端消息进行确认。该协议可以使用服务端的 perpared-statement 功能,即先发送一条参数化 SQL,服务端收到 SQL(Statement)之后对其进行解析、重写并保存,这里保存的 Statement 也就是所谓 Prepared-statement,可以被复用;执行 SQL 时,直接获取事先保存的 Prepared-statement 生成计划并执行,避免对同类型 SQL 重复解析和重写。

如下例, SELECT * FROM users u, logs l WHERE u.usrid=$1 AND u.usrid=l.usrid AND l.date = $2; 是一条参数化 SQL,执行 PREPARE 时,服务端对该 SQL 进行解析和重写;执行 EXECUTE 时,为 Prepared Statement 生成计划并执行。第二次执行 EXECUTE 时无需再对 SQL 进行解析和重写,直接生成计划并执行即可。PostgreSQL Prepared Statement 的具体细节可以参考[3],PostgreSQL JDBC 的相关介绍可以参考[4]。

PREPARE usrrptplan (int) AS
    SELECT * FROM users u, logs l WHERE u.usrid=$1 AND u.usrid=l.usrid
    AND l.date = $2;
EXECUTE usrrptplan(1, current_date);
EXECUTE usrrptplan(2, current_date);

可见,Extended Query 协议通过使用服务端的 Prepared Statement,提升同类 SQL 多次执行的效率。但与 Simple Query 相比,其不允许在一个请求中包含多条 SQL 命令,否则会报语法错误。

Extended Query 协议通常包括 5 个步骤,分别是 Parse,Bind,Describe,Execute 和 Sync。以下分别介绍各个阶段的处理流程。

Parse

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

image.png

PostgreSQL 服务端收到该消息后,调用 exec_parse_message 函数进行处理,进行语法分析、语义分析和重写,同时会创建一个 Plan Cache 的结构,用于缓存后续的执行计划。

Bind

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

image.png

PostgreSQL 收到该消息后,调用 exec_bind_message 函数进行处理。为之前保存的 Prepared Statement 创建执行计划并将其保存在 Plan Cache 中,创建一个 Portal 用于后续执行。关于 Plan Cache 的具体实现和复用逻辑在此不细述,以后单独撰文介绍。

在 PostgreSQL 内核中,Portal 是对查询执行状态的一种抽象,该结构贯穿执行器运行的始终。

Describe

客户端可以发送 Describe 消息获取 Statment 或 Portal 的元信息,即返回结果的列名,类型等信息,这些信息由 RowDescription 消息携带。如果请求获取 Statement 的元信息,还会返回具体的参数信息,由 ParameterDescription 消息携带。

image.png

Execute

客户端发送 Execute 消息告知服务端执行请求,服务端收到消息后,执行 Bind 阶段创建的 Portal,执行结果通过 DataRow 消息返回给客户端,执行完成后发送 CommandComplete 。

image.png

Execute 消息中可以指定返回的行数,若行数为 0,表示返回所有行。

Sync

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

Extended Query 完整的消息流如下:

image.png

Copy 子协议

为高效地导入/导出数据,PostgreSQL 支持 COPY 命令, COPY 操作会将当前连接切换至一种截然不同的子协议。

Copy 子协议对应三种模式:

  • copy-in 导入数据,对应命令 COPY FROM STDIN
  • copy-out 导出数据,对应命令 COPY TO STDOUT
  • copy-both 用于 walsender,在主备间批量传输数据

copy-in 为例,服务端收到 COPY 命令后,进入 COPY 模式,并回复 CopyInResponse。随后客户端通过 CopyData 消息传输数据,CopyComplete 消息标识数据传输完成,服务端收到该消息后,发送 CommandComplete 和 ReadyForQuery 消息,消息流如下:

image.png

总结

本文简要介绍了 PostgreSQL 的通信协议,包括消息格式、消息类型和常见通信过程的消息流。一般通信过程分为两个阶段: startup 阶段创建连接, normal 阶段发送请求并返回结果。 normal 阶段又包括两种子协议, Simple Query 一次性发送查询请求; Extended Query 分阶段发送请求,利用服务端的 prepared statement 特性,提升反复执行同类请求的效率。

PostgreSQL 通信协议中,除本文介绍的 COPY 子协议,还有一些其他的子协议,如主备流复制子协议,限于篇幅,本文并未给出详尽的描述,感兴趣的同学可以参考相关文档[5]。

最后,本文严重参考了 2014 年 PG 大会这篇[6]分享,推荐大家阅读。

参考文献

  1. https://www.net.t-labs.tu-berlin.de/teaching/computer_networking/01.02.htm
  2. https://www.postgresql.org/docs/current/protocol.html
  3. https://www.postgresql.org/docs/12/sql-prepare.html
  4. https://jdbc.postgresql.org/documentation/head/server-prepare.html
  5. https://www.postgresql.org/docs/current/protocol-replication.html
  6. https://www.pgcon.org/2014/schedule/attachments/330_postgres-for-the-wire.pdf
相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
4月前
|
关系型数据库 分布式数据库 数据库
PolarDB PostgreSQL版:Oracle兼容的高性能数据库
PolarDB PostgreSQL版是一款高性能的数据库,具有与Oracle兼容的特性。它采用了分布式架构,可以轻松处理大量的数据,同时还支持多种数据类型和函数,具有高可用性和可扩展性。它还提供了丰富的管理工具和性能优化功能,为企业提供了可靠的数据存储和处理解决方案。PolarDB PostgreSQL版在数据库领域具有很高的竞争力,可以满足各种企业的需求。
|
29天前
|
存储 关系型数据库 数据库
初探PostgreSQL体系结构
初探PostgreSQL体系结构
27 0
|
关系型数据库 PostgreSQL
PostgreSQL VFD机制
PostgreSQL VFD机制
109 0
PostgreSQL VFD机制
|
存储 JSON 分布式计算
「PostgreSQL高级特性」PostgreSQL 数据库的近似算法
「PostgreSQL高级特性」PostgreSQL 数据库的近似算法
|
SQL 关系型数据库 测试技术
PostgreSQL 12 文档: PostgreSQL 客户端工具
PostgreSQL 客户端应用 这部份包含PostgreSQL客户端应用和工具的参考信息。不是所有这些命令都是通用工具,某些需要特殊权限。这些应用的共同特征是它们可以被运行在任何主机上,而不管数据库服务器在哪里。 当在命令行上指定用户和数据库名时,它们的大小写会被保留 — 空格或特殊字符的出现可能需要使用引号。表名和其他标识符的大小写不会被保留并且可能需要使用引号。
205 0
|
关系型数据库 数据库 PostgreSQL
PostgreSQL 12 文档: PostgreSQL 服务端程序
PostgreSQL 服务器应用 这一部分包含PostgreSQL服务器应用和支持工具的参考信息。这些命令只在数据库服务器所在的主机上运行才有用。其他工具程序在PostgreSQL 客户端应用中列出。
94 0
|
SQL 存储 缓存
【视频】PostgreSQL 体系结构 | 学习笔记
快速学习【视频】PostgreSQL 体系结构
【视频】PostgreSQL 体系结构 | 学习笔记
|
监控 关系型数据库 网络安全
PostgreSQL功能介绍
云关系型数据库RDS PostgreSQL
2731 7
|
NoSQL 网络协议 MongoDB
|
存储 关系型数据库 数据库
PostgreSQL体系结构
概述 PostgreSQL数据库是由基于文件系统物理文件建立的,在数据库的运行过程中,整套高效严谨的的逻辑管理着这些物理文件。通常叫这些物理文件为数据库。将这些物理文件、管理这些文件的进程、进程管理的内存 称为这个数据库的实例。
3032 0