当PostgreSQL“听懂”MySQL——协议兼容层的设计与实战

本文整理于 HOW 2026 演讲内容,演讲者:韩伟博,瀚高数据库研发工程师。

前言

在数据库生态迁移的实际场景中,客户的核心诉求始终是少修改、零改造。如果能够在不更换 JDBC/ODBC 驱动的前提下,让应用程序直接连接到 PostgreSQL,迁移成本将被降至最低。

目前市面上存在一些 Proxy 方案,通过中间人代理将 MySQL 协议翻译成 PostgreSQL 协议。这类方案看似简单,但存在明显的缺陷:代理层增加了 CPU 消耗和网络延迟;功能实现不完整,大部分仅支持文本协议(COM_QUERY),对高性能场景所需的二进制协议支持不足,而二进制协议的缺位会导致20 到 30 倍的性能损失

本文基于 HOW 2026 开源生态大会暨 PostgreSQL 高峰论坛的主题分享,系统解析 PostgreSQL 内核层实现 MySQL 协议兼容的设计思路与技术细节,涵盖认证体系、二进制协议实现及核心难点攻克。

一、引言:为什么需要协议兼容?

1.1 数据库生态迁移的核心挑战

在实际的数据库迁移项目中,主要面临以下挑战:

  • 驱动兼容性:应用通过 JDBC/ODBC 驱动与 MySQL 深度绑定,直接迁移需要重写适配层
  • SQL 方言差异 :分页语法(LIMIT vs ROWNUM)、字符串处理函数等在两库间存在显著不同
  • 运维工具链:监控、备份、迁移等运维工具通常仅支持特定数据库生态

1.2 现有 Proxy 方案的局限性

问题维度 具体表现
性能损耗 流量经代理层转发,引入额外网络延迟与 CPU 开销
功能不完整 多数方案仅支持文本协议(COM_QUERY),对二进制协议支持不佳
状态管理复杂 难以在分布式环境下精确维护跨节点的事务与会话状态

1.3 目标:内核级原生兼容

  • 零感知迁移:应用代码、驱动无需任何修改,即可无缝连接 PostgreSQL
  • 性能无损:在内核层直接解析处理协议,彻底规避 Proxy 带来的中间件开销
  • 功能完整覆盖:完整支持 MySQL 的文本协议与高性能二进制协议

二、MySQL 协议基础

2.1 核心报文格式

MySQL 协议基于 TCP 构建,每个数据包由两部分组成:

包头(Packet Header) ------固定 4 字节:

  • 长度(3 字节,小端序):指示包体数据的实际长度
  • 序列号(1 字节):请求/响应周期内递增,用于标记数据包顺序

包体(Packet Body) ------可变长度:

  • 承载具体通信内容,格式由命令类型决定

由于 TCP 是流式协议,会引入粘包/拆包问题,因此所有数据库协议都会在报文中包含长度字段来标记数据边界。

2.2 核心报文类别

类别 说明
连接与认证 HandshakeV10(握手)与 HandshakeResponse(认证响应)
文本协议 COM_QUERY,执行标准文本 SQL,最常用
二进制协议 COM_STMT_PREPARE(准备)与 COM_STMT_EXECUTE(执行),高性能预处理场景

2.3 能力协商机制(Capability Flags)

能力协商是 MySQL 协议动态适配与扩展的核心基石。

核心逻辑:双向确认与交集运算

复制代码
最终生效能力 = 服务器支持能力 ∩ 客户端请求能力

通过位与操作,精准筛选出双方共同兼容的协议特性。

交互流程:

  1. Server Init:服务端发送 HandshakeV10 包,携带自身支持的所有能力标志
  2. Client Resp:客户端解析后,在 HandshakeResponse 中返回请求开启的标志
  3. Negotiate:双方基于位运算达成共识,作为后续通信的协议基准

核心标志位:

  • PROTOCOL_41 (0x00008000):标识使用 4.1+现代协议版本
  • DEPRECATE_EOF (0x01000000):废弃 EOF 包,统一使用 OK 包
  • PLUGIN_AUTH (0x00080000):支持插件认证,是 caching_sha2_password 的基础
  • CLIENT_SSL (0x00000002):表示客户端需要建立 SSL 连接

三、认证体系深度解析

3.1 连接建立与握手流程

第一步:TCP 网络连接建立(三次握手)

  1. Client → Server:SYN,请求建立连接
  2. Server → Client:SYN + ACK,确认收到
  3. Client → Server:ACK,TCP 连接建立完成

第二步:数据库协议握手

  1. Server → Client:HandshakeV10 包(版本号、挑战随机数、能力集)
  2. Client → Server:HandshakeResponse 包(认证响应、字符集、客户端能力)
  3. Server → Client:OK 包(校验通过,连接建立)

关键差异 :PG 是 TCP 建立后客户端 先响应;MySQL 是 TCP 建立后服务端先发送握手包。正是这一差异,使得无法在同一端口上同时监听两种协议。

3.2 HandshakeV10 报文结构

3.3 mysql_native_password 认证解析

这是 MySQL 5.7 及之前版本的默认认证算法,基于经典的挑战-响应 (Challenge-Response)机制,核心采用SHA1算法。

算法流程:

服务器端存储用户密码的两次 SHA1 哈希值:stage2_hash = SHA1(SHA1(password))

客户端计算:

  1. stage1_hash = SHA1(明文密码)
  2. stage2_hash = SHA1(stage1_hash)(与服务端存储一致)
  3. hash3 = SHA1(服务端随机数 + stage2_hash)
  4. auth_response = stage1_hash XOR hash3

服务端验证:

  1. candidate = SHA1(随机数 + stage2_hash)
  2. client_stage1 = auth_response XOR candidate
  3. 验证 SHA1(client_stage1) 是否等于 stage2_hash

PG 内核适配关键改造点:

  • 在 PG 系统表中新增mysql_native_password认证方法标识
  • 用户密码以SHA1(SHA1(password))格式存储
  • 在 PG 认证钩子中插入自定义验证逻辑

3.4 caching_sha2_password 认证解析

这是 MySQL 8.0 引入的默认认证算法,核心算法从 SHA1 升级为SHA256 ,并引入了缓存机制

快速认证路径(Fast Path)

适用场景:客户端之前成功认证过,服务端已缓存用户的stage2_hash

算法逻辑:auth_response = HMAC_SHA256(stage2_hash, scramble) XOR stage2_hash

性能优势:无需 RSA 加解密,计算开销极低。

完整认证路径(Full Path)

适用场景:客户端首次连接,或缓存未命中。

标准流程:

  1. S → C:发送0xFE状态,要求完整认证
  2. C → S:请求服务端公钥
  3. S → C:发送 RSA 公钥
  4. C → S:使用公钥加密明文密码并回传
  5. S → C:私钥解密,计算 Hash 验证,通过后返回 OK

四、核心亮点:全量二进制协议实现

4.1 为什么二进制协议是"重难点"

Proxy 方案的致命短板:

问题 说明
协议解析逻辑复杂 参数绑定与类型编码繁琐,Proxy 难以正确转发二进制包
降级为文本协议 多数 Proxy 将预处理语句转为 COM_QUERY 执行,丧失性能优势
Java 应用兼容性差 useServerPrepStmts=true时,后端不支持会导致连接失败或性能骤降

内核级方案的核心优势:

  • 业务零感知:客户端无需修改任何代码
  • 极致性能:参数在协议层解析后直接传给执行器,避免 SQL 二次解析
  • 从根源杜绝 SQL 注入:参数与 SQL 模板物理分离

4.2 二进制协议六大核心命令

4.3 COM_STMT_PREPARE 的实现

请求格式:

  • Command(1 字节):固定 0x16
  • Query(变长):待预编译的 SQL 字符串

响应构造:

  • PREPARE_OK 包:包含stmt_id、参数个数(num_params)、结果集列数(num_columns)
  • 元数据补充:根据参数/列数量,追加对应的元数据包

核心执行流程:

  1. 识别与翻译 :识别 MySQL 的?占位符,转为 PG 的$N占位符
  2. 内核调用 :调用pg_parse_query等接口进行解析与重写
  3. 状态存储 :将计划与stmt_id关联,存入会话上下文
  4. 响应生成:封装元数据,按 MySQL 协议格式回包

4.4 COM_STMT_EXECUTE 的实现

关键难点一:类型系统映射

MySQL 与 PostgreSQL 的数据类型不一一对应。例如:

  • MySQL 的DATETIME包含微秒,PG 的timestamp包含纳秒
  • MySQL 的JSON本质是字符串,PG 有原生的jsonb类型

解决方案:建立详细的类型映射表 ,在接收参数时将 MySQL 二进制格式转为 PG 的Datum格式,返回结果时逆向转换,重点关注精度转换与跨时区处理。

关键难点二:NULL 位图解析

客户端通过每位 1bit 的位图标记 NULL 值。如果解析错误,会导致后续非 NULL 参数的读取位置整体偏移,引发参数错误甚至程序崩溃。

解决方案:严格遵循 MySQL 协议规范,根据参数总数动态计算位图所需字节数,逐位进行掩码检查,确保准确识别每一个 NULL 参数的位置。

关键难点三:游标处理(setFetchSize)

当 JDBC 调用setFetchSize(n)时,驱动会在协议中设置CURSOR_TYPE_READ_ONLY标志。如果服务端直接返回全量结果集,可能导致内存溢出。

解决方案:在内核检测到游标标志时,创建可保持的游标 (Holdable Cursor),而非一次性 Portal。后续收到COM_STMT_FETCH命令时,按需返回指定行数。

关键难点四:大端/小端适配

MySQL 协议强制使用小端(Little-Endian)字节序,而 PG 内核因运行平台(x86/ARM)不同可能采用不同字节序。

解决方案:在所有协议交互的数据读写点强制进行字节序转换,封装统一的操作工具类,确保发送到客户端的数据严格遵循小端规范。

4.5 性能对比

以 JDBC 多参数查询场景为测试基准:

方案 QPS 表现 延迟表现 说明
原生 MySQL 基准 基准 理论最优性能上限
PG + 协议兼容层 持平 MySQL 更低 内核级解析,无中间件开销,复杂查询受益于 PG 执行器优化
PG + Proxy 方案 显著低于原生 较高 流量经代理转发,存在两次网络跳转及协议二次转换

由于兼容层基于 Libpq 实现,报文收发均在同一进程内完成,网络层面无额外性能损耗。

五、总结与展望

5.1 核心成果

  • 在 PostgreSQL 内核层面实现了对MySQL 5.7 协议的深度兼容
  • 完整适配mysql_native_passwordcaching_sha2_password两大认证机制
  • 攻克二进制协议难点,完美支持预处理语句全生命周期管理
  • 实现应用零改造性能无损生态融合三大核心价值

5.2 未来规划

功能完善:

  • SSL/TLS 支持:实现协议层全链路加密传输(注:PG 原生已支持 SSL,但需确保协议层面适配)
  • 压缩协议 :支持CLIENT_COMPRESS,提升大报文传输效率
  • 命令扩展:完善对 LOAD DATA INFILE 等管理类命令的支持

SQL 方言兼容:

  • 在协议层兼容基础上,逐步完善对 MySQL SQL 方言的识别与转换
  • 最大程度降低应用层代码修改成本

版本兼容:

  • 以 MySQL 5.7 为基准,通过协议中预留的标志位字段,对 MySQL 5.7 和 8.0 进行统一适配与拓展
相关推荐
qq29531 小时前
2026 AI盯盘预警与自选股异动监控工具选型对比
人工智能·区块链
IT_陈寒1 小时前
Java里用Stream.parallel()翻车实录,这性能还不如单线程
前端·人工智能·后端
王国强20091 小时前
OmniDocBench 深度解析:从评测原理到评测开源/闭源 OCR 模型实战
人工智能
小淮AI2 小时前
AI能看财报吗?从18个模型实测到5类工具的使用观察
人工智能
goujunwe2 小时前
GEO 的胜负藏在标签里
人工智能
这个DBA有点耶2 小时前
银行核心系统数据库迁移怎么选?6 步法+5 个避坑指南
数据库·安全·架构
Young丶2 小时前
讲透 Claude Code 系列 (四):Claude Skills 完全指南:可复用的“专业能力包”从入门到精通
人工智能·ai·ai编程·ai coding
风哥2号2 小时前
数据库教程FGMT31‑Linux平台MySQL9.7安装配置与版本升级
数据库