- 后端
- 数据库
- 负载均衡
【免费下载链接】proxysql
High-performance proxy for MySQL and PostgreSQL
CLIENT_DEPRECATE_EOF 是 MySQL 协议中「以 OK 包取代 EOF 包」的关键能力位,ProxySQL 在向后端建连时必须准确表达该偏好,同时严格以服务端握手包(greeting)为准判定真实支持度。本文基于仓库内的实现计划文档 docs/superpowers/plans/2026-08-15-backend-deprecate-eof-negotiation.md 及其配套设计文档 docs/superpowers/specs/2026-08-15-backend-deprecate-eof-negotiation-design.md,完整讲解该协商机制为何需要改造、如何通过 MariaDB Connector/C 补丁与MySQL_Connection代码落地,以及如何用 SQLite3 Server(端口 6030)自环测试把回归面锁定到mysql_real_connect(..., client_flags)这一公开 API 路径。读完本文,你将掌握 ProxySQL 出站 EOF 协商的完整修复思路、可复现的 TAP 验证命令,以及"请求 ≠ 支持"这一兼容性铁律在源码中的具体实现方式。
背景:CLIENT_DEPRECATE_EOF 的能力位之争
在 MySQL 文本协议中,一个查询结果集通常以 EOF 包收尾;CLIENT_DEPRECATE_EOF协商开启后,服务端改为用 OK 包结束结果集,从而减少一次往返并携带更多状态信息。该能力位在 Connector/C 中被定义为CLIENT_DEPRECATE_EOF (1UL << 24)(见补丁对 include/mariadb_com.h 的修改)。
ProxySQL 作为代理存在"双端"协商问题:
- 前端(客户端侧):ProxySQL 作为服务端,是否在握手 greeting 中宣告该能力,受
mysql-enable_client_deprecate_eof控制; - 后端(出站侧):ProxySQL 作为客户端,是否在
mysql_real_connect()调用中请求该能力,决定结果包按 deprecated-EOF(OK 收尾)还是 legacy-EOF(EOF 收尾)解析。
计划文档指出,旧实现存在一个API 不一致:ProxySQL 通过改写 Connector/C 的持久配置字段MYSQL::options.client_flag来启用后端CLIENT_DEPRECATE_EOF,而随包分发的 Connector/C 补丁也基于该配置字段决定是否保留服务端 greeting 宣告的位。问题在于:同一个能力位通过mysql_real_connect()的client_flag参数在握手中发出,Connector/C 却因为读取的是持久配置而丢弃了服务端宣告的位,于是合法的 deprecated-EOF 结果集可能被当作 legacy-EOF 解析——这正是 PR #6076 中 g9 测试作业失败的直接原因(仓库补丁注释亦以 issue #3280 关联该背景)。
核心设计决策:请求是偏好,greeting 才是事实
修复的架构原则浓缩为三句话(设计文档Decision一节):
- ProxySQL 只通过本地
client_flags参数表达出站偏好,不再为这一能力位改写 Connector/C 的持久MYSQL::options.client_flag; - Connector/C 补丁使用其内部已合并的"有效"
client_flag判断是否保留 greeting 位——该值同时包含mysql_real_connect()公开参数与持久选项; - 永远不在
mysql->server_capabilities中合成该位:后端 greeting 未宣告,即便 ProxySQL 请求了,它仍然是 legacy-EOF 后端。
由此得到设计文档中的核心状态矩阵(该矩阵也是后续全部回归测试的判定基准):
| SQLite3 监听端宣告该能力 | ProxySQL 请求该能力 | 期望的后端状态 |
|---|---|---|
| 是 | 是 | server_capabilities保留该位,按 deprecated EOF 解析 |
| 否 | 是 | server_capabilities无该位,按 legacy EOF 解析 |
此外,fast-forward 会话维持更严格策略:只有前端既协商(client_flag含该位)又被 ProxySQL 宣告(前端连接server_capabilities含该位)时,出站请求才带上该位。这条约束被计划列为全局约束之一:"Preserve the fast-forward requirement that frontend negotiation and frontend advertised capability both permit the request"。
落点一:Connector/C 补丁改为判断有效连接标志
计划 Task 2 的第一步是修改 deps/mariadb-client-library/client_deprecate_eof.patch(计划标注的区间为补丁内第 493-501 行),把"检查持久 options"换成"检查有效连接 flags"。当前仓库中该补丁已包含目标实现(第 493-502 行):
/* `client_flag` is the effective request for this connection: mysql_real_connect() * has already merged its public client-flag argument with mysql->options.client_flag. * If that merged request omits CLIENT_DEPRECATE_EOF, hide support advertised by the * server so the remaining result parser consistently expects legacy EOF packets. * This check only removes an unrequested capability from the greeting; it must never * add CLIENT_DEPRECATE_EOF when the server itself did not advertise support. See #3280. */ if ((client_flag & CLIENT_DEPRECATE_EOF) == 0) { mysql->server_capabilities &= ~CLIENT_DEPRECATE_EOF; }这段注释本身就是计划要求"为 vendor patch 添加解释性注释"的落地产物,语义要点有三:
client_flag是mysql_real_connect()公开参数与MYSQL::options.client_flagOR 合并后的有效值,因此无论调用方走哪条路径设置偏好,补丁都能读到一致的事实;- 条件只负责清除未请求的能力位,属于"减法"操作,天然不会伪造服务端支持;
- 后文所有结果解析逻辑(如
ma_net_safe_read、mthd_my_read_rows、mthd_stmt_read_all_rows中的分支)都以mysql->server_capabilities & CLIENT_DEPRECATE_EOF为唯一事实来源,因此补丁在握手阶段修正server_capabilities后,整个解析链路便保持一致。
落点二:ProxySQL 出站请求移入本地连接参数
计划 Task 2 的第二步是改造 lib/mysql_connection.cpp 中的MySQL_Connection::connect_start_SetClientFlag()(计划标注区间为第 903-956 行,仓库当前实际实现位于第 991-1074 行)。该函数负责组装出站mysql_real_connect_start()的client_flags,其决策逻辑上方已按计划要求添加 Doxygen 块(第 1020-1034 行),明确区分三种状态:
/** * @brief Select the backend CLIENT_DEPRECATE_EOF request for this connect attempt. * @details The local connect-call flags express a client preference only. Connector/C * records actual support from the backend greeting in server_capabilities; * a backend that does not advertise the bit remains a legacy-EOF backend. * * @par Normal backend connections * Request CLIENT_DEPRECATE_EOF when mysql-enable_server_deprecate_eof is enabled. * @par Enforced session tracking * Request CLIENT_DEPRECATE_EOF regardless of that setting because session tracking * requires the deprecate-EOF protocol. * @par Fast-forward connections * Replace the normal preference with the frontend's negotiated state. Forward the * request only when the frontend both requested the capability and saw it advertised. */三种状态在源码中的实际实现(lib/mysql_connection.cpp):
- 常规后端连接:
mysql_thread___enable_server_deprecate_eof为真时置位CLIENT_DEPRECATE_EOF; - 强制会话跟踪(ENFORCED):当
mysql_thread___session_track_variables == session_track_variables::ENFORCED时,无条件置位——因为会话跟踪协议本身依赖 deprecated-EOF 语义,该覆盖逻辑与Admin_FlushVariables.cpp中"ENFORCED 覆盖两个开关并打印警告"的行为互相印证; - fast-forward 连接:先无条件清除本地位(
client_flags &= ~CLIENT_DEPRECATE_EOF),仅当前端连接的options.client_flag与options.server_capabilities同时含该位时才重新置位。
组装完成的client_flags在connect_start()中直接作为mysql_real_connect_start()的最后一个参数传出(lib/mysql_connection.cpp):
async_exit_status=mysql_real_connect_start(&ret_mysql, mysql, host_ip, userinfo->username, auth_password, userinfo->schemaname, parent->port, NULL, client_flags);注意整个流程不再向mysql->options.client_flag写入该能力位,这正是"以本地参数而非持久配置表达偏好"的代码级体现。
回归测试设计:两条独立的验证链路
计划采用两条互补的 TAP 测试链路,分别锁定"公开 Connector/C API 路径"和"ProxySQL 出站建连路径"。
链路一:直接 Connector/C 能力矩阵(Task 1)
修改目标为 test/tap/tests/test_sqlite3_special_queries.cpp(计划标注第 15-130 行;当前仓库中矩阵实现在第 117-170 行),配套测试二进制为test_sqlite3_special_queries_libmariadb-t。测试要点:
- 通过 admin 接口的 helper(
set_client_deprecate_eof/get_client_deprecate_eof,见 test_sqlite3_special_queries.cpp)切换mysql-enable_client_deprecate_eof,从而控制 SQLite3 监听端(端口 6030)的 greeting 是否宣告该位; - 对
{宣告, 不宣告}两种状态,都用mysql_real_connect(proxy, ..., SQLITE3_SERVER_PORT, NULL, CLIENT_DEPRECATE_EOF)直接连接(即必须经由公开 API 的client_flags参数,回归面正是这里),然后断言MYSQL::server_capabilities位,再执行SELECT CONNECTION_ID()并断言返回单行数值且等于mysql_thread_id(); - 测试用例名必须与计划一致(当前仓库已照此实现,见 test_sqlite3_special_queries.cpp 与第 163-164 行):
ok(server_supports_deprecate_eof == expected_server_capability, "SQLite3 advertised CLIENT_DEPRECATE_EOF as configured"); ok(connection_id_rc == 0 && valid_connection_id && connection_id == expected_connection_id, "SELECT CONNECTION_ID() parses with the negotiated backend EOF mode");- 关键纪律(Task 1 Step 3):测试代码不得把
CLIENT_DEPRECATE_EOF赋给MYSQL::options.client_flag,必须让mysql_real_connect()的末参数成为唯一回归面; - 补丁合入前运行该二进制预期RED:宣告能力的情形会因为"库收到 deprecated-EOF 结果、却在
mysql_real_connect(..., client_flags)后清掉了 greeting 位"而失败,从而把 PR #6076 的 g9 故障变成可观测的单测回归。
运行命令:
TAP_QUIET_ENVLOAD=1 test/tap/tests/test_sqlite3_special_queries_libmariadb-t链路二:SQLite3 自环后端路径(Task 3)
修改目标为 test/tap/tests/test_match_eof_conn_cap.cpp(计划标注第 1-975 行),配套测试二进制为test_match_eof_conn_cap-t。它利用已有的**自环(self-loop)**配置:把 ProxySQL 自身的 SQLite3 监听端(127.0.0.1:6030,hostgroupSQLITE3_HG,默认 1459,见 test_match_eof_conn_cap.cpp)注册为后端,让 ProxySQL 代理自身,从而在单实例内覆盖所有前端/后端能力组合。
Task 3 的核心是:在既有连接获取路径上,向后端 hostgroup 执行一条返回一行的查询,断言恰好返回一行且值为期望值,并覆盖两个关键状态(test_match_eof_conn_cap.cpp):
{ .cli_depr_eof = true, .srv_depr_eof = true, .force_mismatch = false }, { .cli_depr_eof = false, .srv_depr_eof = true, .force_mismatch = false },其中后者的意义在于证明"请求不会伪造服务端支持":SQLite3 greeting 未宣告该位时,结果仍必须按 legacy EOF 正确解析出一行数据。测试还利用apply_proxy_conf()及其"从磁盘重载全局配置"的清理逻辑,保证每次退出路径都恢复全局配置。
运行命令:
TAP_QUIET_ENVLOAD=1 test/tap/tests/test_match_eof_conn_cap-t计划明确指出两条链路的职责分工:Task 1 的直接 Connector/C 测试是 RED 回归证明(锁定公开 API),Task 3 的自环测试验证独立的 ProxySQL 出站建连路径(锁定connect_start_SetClientFlag与mysql_real_connect_start的组合)。两条链路合起来恰好覆盖设计矩阵的全部四格。仓库中还有一组补充测试位于 test/tap/tests_with_deps/deprecate_eof_support/,覆盖 fast-forward 切换、缓存与混合 flags 等场景,可视为该主题的配套纵深。
配置恢复与测试卫生
计划把"在每条测试退出路径上恢复被修改的全局 MySQL 变量与测试配置"列为全局约束。具体到代码:
- test_sqlite3_special_queries.cpp 用 RAII 类
restore_client_deprecate_eof在析构时把mysql-enable_client_deprecate_eof恢复为原始值; test_match_eof_conn_cap依赖apply_proxy_conf()的清理逻辑从磁盘重载全局配置;- 两套测试同时覆盖 greeting 的两种状态,任一状态下的配置残留都会让下一次运行失去确定性。
关键全局变量与互相制约
CLIENT_DEPRECATE_EOF的双端开关是理解本方案配置面的钥匙:
| 变量 | 作用面 | 默认值 | 源码依据 |
|---|---|---|---|
mysql-enable_client_deprecate_eof | 控制 ProxySQL 前端 greeting 是否宣告该位 | true | lib/MySQL_Thread.cpp 初始化;线程局部变量定义于 include/proxysql_structs.h |
mysql-enable_server_deprecate_eof | 控制 ProxySQL 出站client_flags是否请求该位 | true | 同上 |
mysql-session_track_variables | 取ENFORCED时强制双端启用,无视上述开关 | — | lib/Admin_FlushVariables.cpp 会打印覆盖警告 |
前端 greeting 侧的宣告逻辑位于 lib/MySQL_Protocol.cpp:当mysql_thread___enable_client_deprecate_eof为真、或session_track_variables为ENFORCED时,才在mysql_thread___server_capabilities中置位并写入握手包;否则显式清除。这与后端出站侧的三态决策形成对称设计——前端"宣告"与后端"请求"各自独立受控,由 Connector/C 补丁最终以服务端 greeting 为准裁决真实解析模式。
验证、文档与提交(Task 4)
修复收尾阶段的验证与文档要求同样被计划明确固化:
本地最终验证:
git diff --check TAP_QUIET_ENVLOAD=1 test/tap/tests/test_sqlite3_special_queries_libmariadb-t TAP_QUIET_ENVLOAD=1 test/tap/tests/test_match_eof_conn_cap-t期望两个 TAP 命令均以退出码 0 结束、直接连接测试覆盖两种 greeting 状态、diff 无空白错误。
PR #6076 正文需补充四段解释(计划 Task 4 Step 2):
- 为什么
MYSQL::options.client_flag不是mysql_real_connect(..., client_flags)的安全事实来源; - 为什么 ProxySQL 改为通过本地 connect-call flags 请求该能力;
- 为什么后端 greeting 位缺失时仍按 legacy EOF 解析;
- SQLite3 自环测试矩阵与本地/CI 验证命令。
最终提交信息(计划 Task 4 Step 3 给出的基准文本):
fix: preserve backend deprecate EOF negotiation Request CLIENT_DEPRECATE_EOF through the mysql_real_connect flags rather than mutating Connector/C's persistent options. Retain the capability only when the backend greeting advertises it, so legacy backends continue to use legacy EOF parsing. Add direct Connector/C and SQLite3 self-loop coverage.推送 PR 分支后,需重点检查此前失败的 g9 测试作业;任何无关失败须与本次回归分开报告。
边界与约束清单
最后,计划与设计文档明示的约束可归纳为一份可审计的检查单,适合作为评审清单:
- 禁止合成:任何代码路径都不得向
mysql->server_capabilities添加CLIENT_DEPRECATE_EOF,greeting 是唯一支持来源; - fast-forward 双门槛:前端协商位与前端被宣告位必须同时为真,出站才请求该能力;
- 公开 API 回归面:必须覆盖
mysql_real_connect(..., client_flags)这一导致 PR #6076 g9 失败的确切路径; - 配置恢复:每条测试退出路径都必须恢复被修改的全局变量与测试配置;
- 注释完备:ProxySQL 代码加 Doxygen、vendor patch 加解释性注释;
- 非目标:不为后端无条件启用
CLIENT_DEPRECATE_EOF、不依据请求标志推断服务端支持、不引入新的测试服务或复制既有自环环境。
从当前仓库源码看,计划描述的目标状态(补丁第 493-502 行的有效标志判断、connect_start_SetClientFlag的三态决策与 Doxygen、两条测试链路中的矩阵实现)均已落地,本文所有代码片段均可直接对照仓库验证,是一份可复现、可引用的实现与回归指南。
- 后端
- 数据库
- 负载均衡
【免费下载链接】proxysql
High-performance proxy for MySQL and PostgreSQL
相关推荐
ProxySQL PostgreSQL 后端连接 SSL 参数精细化配置:pgsql_servers_ssl_params 表完全指南
ProxySQL PostgreSQL 后端连接 SSL 参数精细化配置:pgsql_servers_ssl_params 表完全指南 ProxySQL 支持
后端数据库负载均衡ProxySQL SQLite3 监听器 CONNECTION_ID() 与 CLIENT_DEPRECATE_EOF 兼容性实现深度解析
ProxySQL SQLite3 监听器 CONNECTION_ID 与 CLIENT_DEPRECATE_EOF 兼容性实现深度解析 导读 CONNECTIO
后端数据库负载均衡ProxySQL 内置 SQLite3 Server 实战指南:用 MySQL 客户端直连 SQLite 的协议翻译层
ProxySQL 内置 SQLite3 Server 实战指南:用 MySQL 客户端直连 SQLite 的协议翻译层 本指南系统讲解 ProxySQL 内置
后端数据库负载均衡
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考