PostgreSQL 通信协议剖析 原作者:刘东明 创作时间:2020-07-13 15:44:06+08 |
wangliyun 发布于2020-07-14 08:00:06
![]() ![]() ![]() ![]() ![]() |
作者简介
刘东明,阿里云 PostgreSQL 内核研发工程师,花名凌策
我们在使用数据库服务时,通常需要使用客户端连接数据库服务端,以 PostgreSQL 为例,常用的客户端有自带的 psql,JAVA 应用的数据库驱动 JDBC,可视化工具 PgAdmin 等,这些客户端都需要遵守 PostgreSQL 的通信协议才能与之 "交流"。所谓协议,可以理解为一套信息交互规则或者规范,最为我们熟知的莫过于 TCP/IP 协议和 HTTP 协议。
PostgreSQL 在 TCP/IP 协议之上实现了一套基于消息的通信协议,同时,为避免客户端和服务端在同一台机器时的网络通信代价,也支持在 Unix 域套接字上使用该协议。PostgreSQL 至今共实现了三个版本的通信协议,现在普遍使用的是从 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 阶段的两种 "子协议" 在下一节详细介绍。
消息
消息格式
客户端和服务端所有通信都通过消息流进行。消息的第一个字节标识消息类型,随后四个字节标识消息内容的长度(该长度包括这四个字节本身),具体的消息内容由消息类型决定。
需要注意的是,客户端创建连接时,发送的第一条消息,即启动(startup)消息格式有所不同。它没有最开始的消息类型字段,以消息长度开始,随后紧跟协议版本号,然后是键值对形式的连接信息,如用户名、数据库以及其他 GUC 参数和值。
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 阶段是客户端和服务端创建连接的阶段,消息流如下:
客户端首先发送 startup 消息至服务端,服务端判断是否需要授权信息,如若需要,则发送 AuthenticationRequest ,客户端随后发送密码至服务端,权限验证之后,服务端给客户端发送一些参数信息,即 ParameterStatus ,包括 serverversion , clientencoding 和 DateStyle 等。最后,服务端发送一个 ReadyForQuery 消息,告知客户端一切就绪,可以发送请求了。至此,连接创建成功。
取消请求
在 startup 阶段,服务端还会给客户端发送一个 BackendKeyData 消息,该消息中包含服务端的进程 ID 和一个取消码(MyCancelKey)。如果客户端想取消当前正在执行的请求,则可以发送一个 CancelRequset 消息,该消息中包括 startup 阶段服务端提供的进程 ID 和取消码。
取消请求并不是通过当前正在处理请求的连接发送的,而是会创建一个新的连接,创建该连接发送的消息与之前创建连接的消息不同,不再发送 startup 消息,而是发送一个 CancelReqeust 消息,该消息同样没有消息类型字段。
取消请求不保证一定成功,可能服务端接收到取消请求时,当前的查询请求已经结束。取消请求只能在一定程度上加速当前查询结束,如果当前请求被取消,客户端会收到一条错误消息。
发送请求
连接创建之后,通信协议进入 normal 阶段,该阶段的大体流程是:客户端发送查询请求,服务端接收请求、处理请求并将结果返回给客户端。上文提到,该阶段有两种 "子协议",本节分别介绍这两种 "子协议" 的消息流。
Simple Query
客户端通过 Query 消息发送一个文本命令给服务端,服务端处理请求,回复查询结果。查询结果通常包括两部分内容:结构和数据。结构通过 RowDescription 消息传递,包括列名、类型 OID 和长度等;数据通过 DataRow 消息传递,每个 DataRow 消息中包含一行数据。
每个命令的结果发送完成之后,服务端会发送一条 CommandComplete 消息,表示当前命令执行完成。客户端的一条查询请求可能包含多条 SQL 命令,每个 SQL 命令执行完都会回复一条 CommandComplete 消息,查询请求执行结束后会回复一条 ReadyForQuery 消息,告知客户端可以发送新的请求。消息流如下:
注意,一个请求中的多条 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 消息前将其显式销毁。
PostgreSQL 服务端收到该消息后,调用 execparsemessage 函数进行处理,进行语法分析、语义分析和重写,同时会创建一个 Plan Cache 的结构,用于缓存后续的执行计划。
Bind
客户端发送 Bind 消息,该消息携带具体的参数值、参数格式和返回列的格式,如下:
PostgreSQL 收到该消息后,调用 execbindmessage 函数进行处理。为之前保存的 Prepared Statement 创建执行计划并将其保存在 Plan Cache 中,创建一个 Portal 用于后续执行。关于 Plan Cache 的具体实现和复用逻辑在此不细述,以后单独撰文介绍。
在 PostgreSQL 内核中,Portal 是对查询执行状态的一种抽象,该结构贯穿执行器运行的始终。
Describe
客户端可以发送 Describe 消息获取 Statment 或 Portal 的元信息,即返回结果的列名,类型等信息,这些信息由 RowDescription 消息携带。如果请求获取 Statement 的元信息,还会返回具体的参数信息,由 ParameterDescription 消息携带。
Execute
客户端发送 Execute 消息告知服务端执行请求,服务端收到消息后,执行 Bind 阶段创建的 Portal,执行结果通过 DataRow 消息返回给客户端,执行完成后发送 CommandComplete 。
Execute 消息中可以指定返回的行数,若行数为 0,表示返回所有行。
Sync
使用 Extended Query 协议时,一个请求总是以 Sync 消息结束,服务端接收到 Sync 消息后,关闭隐式开启的事务并回复 ReadyForQuery 消息。
Extended Query 完整的消息流如下:
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 消息,消息流如下:
总结
本文简要介绍了 PostgreSQL 的通信协议,包括消息格式、消息类型和常见通信过程的消息流。一般通信过程分为两个阶段:startup 阶段创建连接, normal 阶段发送请求并返回结果。normal 阶段又包括两种子协议, Simple Query 一次性发送查询请求;Extended Query 分阶段发送请求,利用服务端的 prepared statement 特性,提升反复执行同类请求的效率。
PostgreSQL 通信协议中,除本文介绍的 COPY 子协议,还有一些其他的子协议,如主备流复制子协议,限于篇幅,本文并未给出详尽的描述,感兴趣的同学可以参考相关文档[5]。
本文参考了 2014 年 PostgreSQL 大会这篇[6]分享,推荐大家阅读。
最后,欢迎大家使用阿里云 PostgreSQL 数据库系列产品:
RDS PostgreSQL:https://www.aliyun.com/product/rds/postgresql
PolarDB:https://www.aliyun.com/product/polardb
参考文献
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
请在登录后发表评论,否则无法保存。
1# __
xcvxcvsdf 回答于 2024-11-14 08:58:05+08
https://su.tiancebbs.cn/hjzl/456511.html
https://hzhaizhu.tiancebbs.cn/qths/468050.html
https://aihuishou.tiancebbs.cn/sh/858.html
https://zulin.tiancebbs.cn/sh/303.html
https://sh.tiancebbs.cn/hjzl/456972.html
https://aihuishou.tiancebbs.cn/sh/1454.html
https://bj.tiancebbs.cn/lywhs/149777.html
https://aihuishou.tiancebbs.cn/sh/2600.html
https://zulin.tiancebbs.cn/sh/2534.html
https://aihuishou.tiancebbs.cn/sh/4675.html
https://www.tiancebbs.cn/ershouwang/472594.html
https://zulin.tiancebbs.cn/sh/2188.html
https://zulin.tiancebbs.cn/sh/2856.html
https://su.tiancebbs.cn/hjzl/463478.html
https://zulin.tiancebbs.cn/sh/3487.html
https://www.tiancebbs.cn/ershouwang/467685.html
https://aihuishou.tiancebbs.cn/sh/2525.html
2# __
xcvxcvsdf 回答于 2024-10-18 10:43:02+08
https://www.tiancebbs.cn/ershouwang/474339.html
https://hz.tiancebbs.cn/hzwf/57087.html
https://zulin.tiancebbs.cn/sh/4701.html
https://aihuishou.tiancebbs.cn/sh/3857.html
https://taicang.tiancebbs.cn/hjzl/459897.html
https://aihuishou.tiancebbs.cn/sh/2408.html
https://zulin.tiancebbs.cn/sh/2780.html
https://al.tiancebbs.cn/jiajiao/213082.html
https://aihuishou.tiancebbs.cn/sh/1406.html
https://www.tiancebbs.cn/ershoufang/468936.html
https://changshushi.tiancebbs.cn/hjzl/457047.html
https://su.tiancebbs.cn/hjzl/469169.html
https://taicang.tiancebbs.cn/hjzl/465147.html
https://aihuishou.tiancebbs.cn/sh/1311.html
https://aihuishou.tiancebbs.cn/sh/136.html
https://gxqz.tiancebbs.cn/qths/467017.html
https://su.tiancebbs.cn/hjzl/464828.html
3# __
xiaowu 回答于 2024-04-24 10:41:40+08
综治述职报告:https://www.nanss.com/gongzuo/19138.html 法定代表人身份证明:https://www.nanss.com/gongzuo/19146.html 早安最暖心问候语:https://www.nanss.com/yulu/19978.html 我是猫读后感:https://www.nanss.com/xuexi/19618.html 委托书如何写:https://www.nanss.com/shenghuo/18259.html model是什么意思:https://www.nanss.com/xuexi/18173.html 学校中层述职报告:https://www.nanss.com/gongzuo/19715.html 房屋装修合同:https://www.nanss.com/shenghuo/19319.html 一年级家长寄语:https://www.nanss.com/wenan/18645.html 研讨会邀请函:https://www.nanss.com/gongzuo/18873.html 粉色康乃馨的花语:https://www.nanss.com/jiaju/18923.html 服装销售技巧和话术:https://www.nanss.com/shenghuo/19212.html 意大利的首都是:https://www.nanss.com/wenti/18931.html 妇女定义:https://www.nanss.com/shenghuo/18515.html 财务收支审计报告:https://www.nanss.com/gongzuo/18885.html 布朗族:https://www.nanss.com/shenghuo/18362.html 读书的名言警句:https://www.nanss.com/yulu/18188.html 夏至的寓意和象征:https://www.nanss.com/shenghuo/18977.html 共青团工作总结:https://www.nanss.com/gongzuo/18433.html 三年级数学试卷分析:https://www.nanss.com/xuexi/18605.html 300字美文摘抄:https://www.nanss.com/yuedu/18649.html 满汉全席多少道菜:https://www.nanss.com/wenti/19052.html 小马过河教案:https://www.nanss.com/gongzuo/18595.html 猪肚炖什么好吃又营养又养胃:https://www.nanss.com/wenti/19507.html 应急预案演练总结:https://www.nanss.com/gongzuo/18712.html 市场行情分析报告:https://www.nanss.com/gongzuo/18438.html 小升初简历:https://www.nanss.com/xuexi/20015.html 新闻通讯稿:https://www.nanss.com/shenghuo/19079.html 缩写故事400字作文:https://www.nanss.com/xuexi/18666.html 第一届春晚是哪一年:https://www.nanss.com/wenti/18401.html
发表评论:
扫码关注
© PostgreSQL中文社区 ... (自2010年起)