跳到主要内容
浏览器
导航
数据库浏览器

MySQL 与 PostgreSQL 通信协议

客户端与数据库之间交换的是按协议封装的消息。本文说明 MySQL 报文的长度与序号结构、握手认证阶段、文本与二进制协议、预处理语句的往返,以及 PostgreSQL 的前后端消息格式、扩展查询协议与异步通知机制。

发布于 5 分钟阅读 1740 字

客户端与数据库服务器之间交换的不是函数调用,而是字节流上的消息。消息的封装方式决定了协议如何分帧、如何标明一段负载是文本结果还是二进制结果、以及同一连接上能否以流水线方式交错多个请求。两种协议族在这些问题上的处理方式不同,差异又进一步影响到预处理语句与多语句执行的支持形式。

MySQL 报文的封装

每个报文由 4 字节头部与负载构成:前 3 字节以小端表示负载长度,第 4 字节为序号。序号在同一命令的往来中逐条递增,两端各自维护计数,用于检测丢包或错位。

部分长度含义
长度3 字节负载字节数,上界为 16MB 减 1
序号1 字节同一命令内的报文序号,回绕后归零
负载变长实际内容,类型由负载首字节标识

由于单报文负载有长度上界,超长值会被拆成多个报文续传,接收方按长度拼接。负载首字节通常标识报文类型,例如 OK、错误、结果集列定义与行数据。

握手、认证与能力协商

连接建立时由服务端发出初始握手报文,其中包含协议版本、服务端版本字符串、连接标识、能力标志、字符集与认证插件名。客户端回应一个认证报文,携带用户名、认证响应、目标数据库名与自身的能力标志。

能力标志是协商的核心:双方各自给出支持的位集,按交集启用特性,例如协议压缩、加密传输、插件式认证与多语句。认证方式由插件协商,口令校验与挑战响应属于不同的认证路径;需要切换时,服务端会发出切换认证的报文,客户端据此完成一次额外的往返。

文本协议与二进制协议

结果集的编码方式有两种:

  • 文本协议:所有列值以字符串形式传输,客户端按列定义中的类型信息自行转换。普通查询结果与部分管理命令使用这种形式。
  • 二进制协议:列值以二进制表示传输,整型按原位宽写出,浮点按 IEEE 754 布局,字符串仍为长度前缀加字节。预处理语句的结果集使用这种形式。

二进制形式省去了数值与字符串之间的转换,也不依赖字符串到类型的推断;代价是要求两端对类型映射的理解一致。

预处理语句的往返

预处理语句把执行拆成两个阶段。准备阶段客户端发送准备命令与语句文本,服务端返回语句标识、参数个数与结果列信息;执行阶段客户端发送执行命令,携带语句标识、参数的类型与值,以及是否需要重新绑定类型的标志。

由于参数以二进制形式传输并带有类型信息,值不必经过文本拼接,重复执行时只需重发参数部分。关闭语句通过对应的关闭命令释放服务端结构,只做准备而不关闭会使语句缓存持续占用内存。

# 抓包中可见的报文顺序(示意)
# 服务端 -> 客户端  初始握手(协议版本、能力标志、认证插件)
# 客户端 -> 服务端  认证响应(用户名、认证数据)
# 客户端 -> 服务端  COM_QUERY 或 COM_STMT_PREPARE
# 服务端 -> 客户端  列定义报文序列 + 行数据报文序列 + OK

PostgreSQL 的消息格式

PostgreSQL 的消息封装方式不同:没有统一的长度前缀,而是由 1 字节消息类型加 4 字节长度(含长度字段自身)组成。启动阶段是例外,客户端发出的是不带类型字节的启动消息,其中包含协议版本与参数键值对;此后所有消息都带类型字节。

消息按方向命名。客户端到服务端的常见消息包括查询、解析、绑定、执行、同步与终止;服务端到客户端的包括就绪、行描述、数据行、命令完成、错误与通知响应等。类型字节让解析器可以用一张消息表分发处理,新增消息类型不影响既有解析逻辑。

简单查询与扩展查询

简单查询把整条 SQL 文本放进一条查询消息,服务端解析、执行并返回结果,之后发出一条就绪消息。它在一次往返内完成,但不支持参数绑定;以分号分隔的多条语句会被当作一个隐式事务块处理。

扩展查询把过程拆成可分离的步骤,其中的解析阶段正是语句文本转为语法树的位置,可参见 SQL 解析与抽象语法树:

  1. 解析:发送语句文本与参数类型的提示,服务端返回解析完成或错误
  2. 绑定:把参数值绑定到已解析语句,生成一个门户,返回绑定完成
  3. 执行:执行门户,返回行数据与命令完成
  4. 同步:要求服务端结束当前错误状态并发送就绪消息

这些步骤可以在一条管道中连续传递,因此准备与执行能够合并到一次网络往返中。未命名的语句与门户在下一次解析或绑定时被覆盖,命名语句与门户常驻直到被显式关闭或会话结束。多数客户端库把「缓存已解析语句」与「复用门户」分开处理,因为门户的生命周期往往与事务绑定。

异步通知

LISTEN 与 NOTIFY 让连接在空闲时也能收到服务端推送。客户端执行监听命令后,某个会话调用通知命令时,服务端会向所有监听该频道的连接发送通知响应消息。这条消息可能插入在任意返回序列之间,因此客户端解析循环需要区分「当前命令的应答」与「与命令无关的异步消息」,并把后者交给独立的处理路径。

小结

两种协议都把消息切分为带边界的帧,差别在分帧方式与执行粒度上:一方用统一的长度前缀与序号,用命令码区分语义,预处理语句以语句标识为句柄;另一方用消息类型字节加长度,把查询拆成解析、绑定、执行等可组合的步骤,并以门户作为执行单位。

数据库浏览器 · 相关文章