本文整理于 HOW 2026 演讲内容,演讲者:韩伟博,瀚高数据库研发工程师。
前言
在数据库生态迁移的实际场景中,客户的核心诉求始终是少修改、零改造。如果能够在不更换 JDBC/ODBC 驱动的前提下,让应用程序直接连接到 PostgreSQL,迁移成本将被降至最低。
目前市面上存在一些 Proxy 方案,通过中间人代理将 MySQL 协议翻译成 PostgreSQL 协议。这类方案看似简单,但存在明显的缺陷:代理层增加了 CPU 消耗和网络延迟;功能实现不完整,大部分仅支持文本协议(COM_QUERY),对高性能场景所需的二进制协议支持不足,而二进制协议的缺位会导致20 到 30 倍的性能损失。
本文基于 HOW 2026 开源生态大会暨 PostgreSQL 高峰论坛的主题分享,系统解析 PostgreSQL 内核层实现 MySQL 协议兼容的设计思路与技术细节,涵盖认证体系、二进制协议实现及核心难点攻克。
一、引言:为什么需要协议兼容?
1.1 数据库生态迁移的核心挑战
在实际的数据库迁移项目中,主要面临以下挑战:
- 驱动兼容性:应用通过 JDBC/ODBC 驱动与 MySQL 深度绑定,直接迁移需要重写适配层
- SQL 方言差异 :分页语法(
LIMITvsROWNUM)、字符串处理函数等在两库间存在显著不同 - 运维工具链:监控、备份、迁移等运维工具通常仅支持特定数据库生态
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 协议动态适配与扩展的核心基石。
核心逻辑:双向确认与交集运算
最终生效能力 = 服务器支持能力 ∩ 客户端请求能力
通过位与操作,精准筛选出双方共同兼容的协议特性。
交互流程:
- Server Init:服务端发送 HandshakeV10 包,携带自身支持的所有能力标志
- Client Resp:客户端解析后,在 HandshakeResponse 中返回请求开启的标志
- Negotiate:双方基于位运算达成共识,作为后续通信的协议基准
核心标志位:
PROTOCOL_41 (0x00008000):标识使用 4.1+现代协议版本DEPRECATE_EOF (0x01000000):废弃 EOF 包,统一使用 OK 包PLUGIN_AUTH (0x00080000):支持插件认证,是 caching_sha2_password 的基础CLIENT_SSL (0x00000002):表示客户端需要建立 SSL 连接
三、认证体系深度解析
3.1 连接建立与握手流程
第一步:TCP 网络连接建立(三次握手)
- Client → Server:SYN,请求建立连接
- Server → Client:SYN + ACK,确认收到
- Client → Server:ACK,TCP 连接建立完成
第二步:数据库协议握手
- Server → Client:HandshakeV10 包(版本号、挑战随机数、能力集)
- Client → Server:HandshakeResponse 包(认证响应、字符集、客户端能力)
- 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))
客户端计算:
stage1_hash = SHA1(明文密码)stage2_hash = SHA1(stage1_hash)(与服务端存储一致)hash3 = SHA1(服务端随机数 + stage2_hash)auth_response = stage1_hash XOR hash3
服务端验证:
candidate = SHA1(随机数 + stage2_hash)client_stage1 = auth_response XOR candidate- 验证
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)
适用场景:客户端首次连接,或缓存未命中。
标准流程:
- S → C:发送
0xFE状态,要求完整认证 - C → S:请求服务端公钥
- S → C:发送 RSA 公钥
- C → S:使用公钥加密明文密码并回传
- 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) - 元数据补充:根据参数/列数量,追加对应的元数据包
核心执行流程:
- 识别与翻译 :识别 MySQL 的
?占位符,转为 PG 的$N占位符 - 内核调用 :调用
pg_parse_query等接口进行解析与重写 - 状态存储 :将计划与
stmt_id关联,存入会话上下文 - 响应生成:封装元数据,按 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_password与caching_sha2_password两大认证机制 - 攻克二进制协议难点,完美支持预处理语句全生命周期管理
- 实现应用零改造 、性能无损 、生态融合三大核心价值
5.2 未来规划
功能完善:
- SSL/TLS 支持:实现协议层全链路加密传输(注:PG 原生已支持 SSL,但需确保协议层面适配)
- 压缩协议 :支持
CLIENT_COMPRESS,提升大报文传输效率 - 命令扩展:完善对 LOAD DATA INFILE 等管理类命令的支持
SQL 方言兼容:
- 在协议层兼容基础上,逐步完善对 MySQL SQL 方言的识别与转换
- 最大程度降低应用层代码修改成本
版本兼容:
- 以 MySQL 5.7 为基准,通过协议中预留的标志位字段,对 MySQL 5.7 和 8.0 进行统一适配与拓展