Vitess v23.0.6 发布详解:VReplication、VTGate 表达式引擎与复制链路的关键修复
2026/9/21 19:12:44 网站建设 项目流程

Vitess v23.0.6 发布详解:VReplication、VTGate 表达式引擎与复制链路的关键修复

【免费下载链接】vitessVitess is a database clustering system for horizontal scaling of MySQL.项目地址: https://gitcode.com/gh_mirrors/vi/vitess

本篇文章基于 Vitess 仓库 changelog/23.0/23.0.6/changelog.md 整理,系统解读 v23.0.6 补丁版本的全部变更。该版本属于 release-23.0 分支的第六个补丁版,聚焦于稳定性与正确性修复,覆盖 VReplication/VDiff 数据流、VTGate 的 evalengine 表达式引擎、VTTablet 事务节流与心跳、reparent 候选排序,以及 CI 与安全加固。读完本文,你将能按模块理解每一项修复的触发场景、底层原理(含源码证据)以及升级后应当关注的行为变化。

版本总览与变更分布

v23.0.6 是一个以"修复"为主的补丁版本,按分类统计大致如下:

  • Bug fixes(缺陷修复):覆盖 Build/CI、Documentation、Query Serving、VDiff、VReplication、VTGate、VTTablet、vtctl 多个模块,是本版本的主体;
  • CI/Build(构建与持续集成):修复 mysql57 环境依赖、change-detection 过滤器自引用、归档站点下载失败重试;
  • Compatibility Bug(兼容性缺陷):ENUM 在数值上下文中的序数语义对齐 MySQL;
  • Dependencies(依赖升级):Docker 构建镜像 Go 版本升级到go1.25.12go1.25.13
  • Enhancement(增强):备份恢复强制TZ=UTC、mysql 协议流式错误语义改进;
  • Internal Cleanup / Release / Security:维护者列表更新、v23.0.6 代码冻结、CI 权限最小化与密钥传递加固。

从变更密度看,VTGate(含 evalengine)与 VReplication是本次修复最集中的两个领域,下文逐一展开。

VReplication 与 VDiff:数据流正确性修复

VReplication 是 Vitess 实现在线迁移(MoveTables、Reshard)与持续数据同步的核心机制,v23.0.6 对其做了多项细粒度修复。

SET 列第 64 个成员的丢失问题

[release-23.0] vstreamer: don't drop a SET column's 64th member (#20450)修复了一个位于 binlog 流解析层的边界 bug。

MySQL 的 SET 列最多可定义 64 个成员,binlog 事件中以一个 64 位无符号整数(位图)表示。vstreamer 在把整数位图还原成字符串时,必须逐位检查这 64 个 bit。从源码看,实现位于 vstreamer.go 的buildSetStringValue

// A SET column can have 64 unique values ... // For this reason the binlog event contains the values encoded as an unsigned 64-bit // integer which is really a bitmap. iv, err := value.ToUint64() ... idx := 1 // See what bits are set in the bitmap using bitmasks. A SET can have up to // 64 members, so we need to check all 64 bits, including bit 1<<63 for the // 64th member. The loop terminates when the shift overflows back to 0. for b := uint64(1); b != 0; b <<= 1 { if iv&b > 0 { // This bit is set and the SET's string value needs to be provided. ... } idx++ }

此前实现如果漏掉了1<<63这一最高位(或位宽判断有误),那么定义了 64 个成员的 SET 列在第 64 个值被写入时,vstreamer 解码出的字符串会缺少该成员,进而导致下游写入的数据与源库不一致。修复后循环依赖位图位移溢出(b <<= 1溢出回 0)来覆盖全部 64 位,保证第 64 个成员也被正确映射。

JSON 差异路径转义与 GetTenantClause 标识符转义

  • binlog: escape JSON diff paths when generating diff SQL (#20461):VReplication 在检测到 JSON 列内容差异并生成 diff SQL 时,需要转义 JSON 路径中的特殊字符,否则路径中包含引号、反斜杠等字符时生成的 SQL 会被错误解析。
  • workflow: escape tenant id and column name in getTenantClause (#20413)getTenantClause用于构造按租户过滤的查询子句,修复了租户 ID 与列名未转义可能导致的 SQL 注入/语法错误风险。

vplayer 批量写入的正确性

VReplication: Avoid sending mixed batch of row changes to bulk insert or bulk delete in vplayer batch mode (#20565)VReplication: only build a bulk-delete plan for insertNormal table plans (#20889)共同完善了 vplayer 批处理路径:

  • 批量模式下,同一批次内不能同时混入"批量插入"与"批量删除"两类行变更,否则生成的目标 SQL 语义会混乱;
  • bulk-delete 计划只允许为insertNormal类型的表计划构建,避免在其它计划形态下误用批量删除导致数据被意外删除。

这两项修复直接关系到高吞吐迁移/同步场景下的数据一致性,属于对批处理执行器(vplayer)边界条件的收敛。

节流时间不计入 vplayer 停滞期限

VReplication: don't count throttled time in the vplayer stall deadline (#20925):vplayer 设有停滞(stall)截止时间以检测无进展的复制任务,但节流(throttled)期间本身是有意放慢速度而非故障。修复前若把节流时间计入停滞期限,长时间节流会被误判为停滞而中断复制。修复后停滞判定只统计非节流的时间,保证节流机制与停滞检测互不干扰。

工作流状态指标与持久化行对齐

VReplication: reconcile in-memory workflow state metric from the persisted row (#20442):VReplication 工作流的内存态指标与持久化的状态行可能出现分歧(例如进程重启后内存态未刷新),修复后以持久化行为准进行对账(reconcile),确保workflow show等查询反映真实状态。

VDiff 修复

  • VDiff: stop pre-seeding a bogus category in TableDiffPhaseTimings (#20713):表 diff 各阶段计时指标(TableDiffPhaseTimings)不再预置一个无意义的类别,避免监控面板上出现虚假的阶段数据;
  • Properly handle vstream filter predicates with multi-col PKs (#20858):当 vstream 的过滤谓词涉及多列主键时,VDiff 的谓词计算与行匹配此前可能出错,本次修复保证多列主键表在 VDiff 过滤场景下的正确比对。

VTGate 与 evalengine:表达式引擎行为对齐 MySQL

evalengine 是 VTGate 内置的 MySQL 兼容表达式求值引擎,位于 go/vt/vtgate/evalengine。v23.0.6 针对它做了大量"抠细节"的兼容性修复。

TO_BASE64 在 57 字节整数倍处的多余 NUL 字节

evalengine: don't append a NUL byte to TO_BASE64 output at exact 57 byte multiples (#20474)

MySQL 的TO_BASE64()每编码 76 个字符(对应 57 字节输入)插入一个换行符。在 fn_base64.go 中可见其实现约定:

// MySQL wraps every 76 characters with a newline. That maps // to a 57 byte input. So we encode here in blocks of 57 bytes // with then each a newline. var ( mysqlBase64OutLineLength = 76 mysqlBase64InLineLength = (mysqlBase64OutLineLength / 4) * 3 ) func mysqlBase64Encode(in []byte) []byte { // A newline is only written after a full 57 byte block that has more // input following it, so an input that is an exact multiple of 57 // bytes gets one newline less than len(in)/57. newlines := 0 if len(in) > 0 { newlines = (len(in) - 1) / mysqlBase64InLineLength } ... }

修复点在于:当输入长度恰好为 57 的整数倍时,输出末尾不应再有额外的字节(此前实现可能多写一个 NUL 字节),导致编码结果与 MySQL 不一致。

ROUND() 对 DOUBLE 的"四舍六入五成双"

evalengine: round DOUBLE ties half to even in ROUND() like MySQL (#20476):MySQL 对浮点ROUND()采用"舍入到最接近偶数"(round half to even,即银行家舍入),而此前 evalengine 对恰好处于中间值的 DOUBLE 结果可能按其它规则舍入。此修复保证ROUND(2.5)ROUND(1.5)等边界值行为与 MySQL 完全一致,避免因舍入差异引发的数据不一致。

二进制协议中的零值日期

mysql: encode zero DATE/DATETIME/TIMESTAMP values as zero-length in the binary protocol (#20460):在使用预处理语句(binary protocol)时,零值日期(如'0000-00-00')此前可能被编码为错误长度,修复后按 MySQL 语义以零长度编码,保证客户端收到的类型与长度正确。

表达式求值的内存安全与 NULL 语义

  • evalengine: stop RPAD growing its operand in place (#20697)RPAD在填充时不再原地扩展操作数,避免因原地修改缓冲区影响共享的表达式结果;
  • evalengine: stop the binary bitwise operators writing over and marking their result (#20696):二进制按位运算(&|^等)不再写覆盖并标记其操作数结果,防止修改了可能被复用的中间值;
  • evalengine: collapse the VM stack when a later LOCATE or SUBSTRING operand is NULL (#20645):当LOCATE/SUBSTRING的靠后操作数为 NULL 时,及时折叠(collapse)虚拟机栈,避免栈上残留无效条目影响后续表达式求值。

这三项都属于 evalengine 编译/执行栈的健壮性修复,尤其影响复杂的嵌套表达式。

ENUM 在数值上下文中的 1 起始序数

evalengine: use MySQL 1-based ordinal for ENUM in numeric context (#20454):MySQL 中 ENUM 在数值上下文中返回从1开始的序数(如SELECT 'b' + 0,当 ENUM 定义为('a','b')时返回 2)。此前 evalengine 可能按 0 起始计算,本次修复使其与 MySQL 语义对齐。

递归 CTE 的流式深度限制

Enforce recursion-depth limit on the streaming recursive CTE path (#20432):递归 CTE 若缺少终止条件会无限迭代,v23.0.6 将深度上限也施加到流式执行路径。在 recurse_cte.go 中:

// maxRecurseDepth caps the number of recursion iterations before we abort. // TODO: This should be controlled with the cte_max_recursion_depth system variable. const maxRecurseDepth = 1000

TryExecuteTryStreamExecute两条路径都在每次迭代时执行loops > maxRecurseDepth检查,超限则返回vterrors.VT09030("")(见 recurse_cte.go)。此前仅缓冲路径受保护,流式路径缺失该限制时可能被恶意/错误的递归查询拖垮。对应测试位于 recurse_cte_test.go。

流式执行的多处状态隔离修复

  • vtgate: copy bindVars per source in streaming Concatenate (#20436):流式Concatenate(多个源结果串联)中,不同源的 bindVars 若共享同一 map,前一源的改动会泄漏到后一源;修复后每个源拷贝独立的 bindVars;
  • vtgate: copy execute options per scatter call for FetchLastInsertId (#20439)FetchLastInsertId在跨分片散射(scatter)时每个分片调用需独立的执行选项(execute options),避免共享导致的状态污染;
  • vtgate: do not autocommit per-chunk in streaming insert-select (#20497):流式INSERT ... SELECT不再对每个数据块单独 autocommit,避免中途失败时留下半提交的部分数据,保证事务语义与批量路径一致。

预处理语句与查询提示

  • vtgate: support prepared statements with a leading WITH clause (#20665):预处理语句支持以WITH开头的语句(即 CTE 形式的预处理语句),扩展了协议兼容面;
  • Preserve query hints in field/impossible queries (#20366):在生成字段查询(field query)与不可能查询(impossible query)时保留查询提示(hint),避免 EXPLAIN 或元数据探测阶段丢失优化器提示。

客户端连接与超时语义

  • Fix ExecuteMulti timeout context reuse (#20445)ExecuteMulti不再复用已被取消/过期的超时 context,防止后续请求继承上一个请求的截止时间而立即超时;
  • grpcvtgateconn: stop mutating shared dial options slice (#20580):gRPC 客户端不再修改共享的 dial options 切片,消除并发初始化时的数据竞争。

VTTablet:事务节流、心跳与执行安全

txthrottler 在 target_replication_lag_sec=1 时不再 panic

txthrottler: don't panic on target_replication_lag_sec of 1 (#20554)修复了一个由"整数除法下取整为 0"引发的崩溃。在 tx_throttler.go 中:

return ts.maxLag.Load() > ts.config.TxThrottlerConfig.TargetReplicationLagSec && ... ... ticker := time.NewTicker(time.Duration(ts.config.TxThrottlerConfig.TargetReplicationLagSec) * time.Second / 2)

TargetReplicationLagSec配置为 1 秒时,1 * time.Second / 2取整为 0,time.NewTicker(0)会立即 panic。对应测试见 tx_throttler_test.go,明确注释"旧代码把 TargetReplicationLagSec/2 向下取整为 0"。修复后该配置值不再触发崩溃,事务节流可安全使用 1 秒目标延迟。

取消上下文下的 schema 重载回滚

vttablet: roll back canceled schema reload on a non-cancelable context (#20385):当一次 schema 重载因 context 取消而失败时,若后续使用不可取消的 context 继续执行,需要回滚这次"被取消"的重载状态,避免表结构信息处于半更新状态。

畸形 RowChange 镜像不再引发 panic

vreplication: prevent vttablet panic on malformed RowChange images (#20377):vstream 接收到的 binlogRowChange镜像若字段数量与表结构不匹配(畸形数据),此前可能导致 vttablet panic,本次修复改为返回错误并跳过,保证复制进程存活。

流式 CALL 路径的存储过程安全检查

Enforce stored-procedure safety checks on the streaming CALL path (#20372):vttablet 对存储过程CALL有安全检查(例如禁止某些危险调用),此前该检查只覆盖部分路径,流式CALL可能绕过,本次在流式路径上强制同样约束。

心跳读取器初始读取

vttablet: perform an initial heartbeat read when opening the heartbeat reader (#20868):心跳读取器(heartbeat reader)打开时立即执行一次初始读取,避免启动后长时间无心跳数据导致主从延迟(lag)指标误报或延迟过高判断。

reparent 候选排序与复制管理

按 GTID 支配关系排序候选

reparentutil: order reparent candidates by GTID dominance for a consistent sort (#20728)(同时更新了文档)与reparentutil: keep nil-alias tablets out of candidate ordering (#20762)共同改进了 reparent(主从切换)时候选 tablet 的排序逻辑。

GTID 位置是偏序(partially ordered)关系:两个候选可能互不支配(如 UUID 集合不相交),直接两两比较会导致排序结果不稳定。修复实现在 reparent_sorter.go 的comparePosition

// comparePosition orders by replication position using the precomputed dominated // counts (how many other candidates strictly dominate each one). A lower count is // more advanced. Combined dominance takes precedence; at an equal count it falls // through to the Executed-dominated count ... // GTID positions are only partially ordered, so counts are not unique: incomparable // candidates (disjoint UUIDs) can share a count, in which case comparePosition // returns 0 and leaves the decision to the next tiebreaker. Counting dominators // keeps the sort transitive-safe where a naive pairwise comparison is not; // findMostAdvanced still re-checks the winner after sorting.

核心思路:预计算每个候选被多少其它候选严格支配(dominated count),支配数越少越先进;同时以 Combined(已接收)支配数优先、Executed(已执行)支配数次之,兼顾复制延迟。计数排序避免了朴素两两比较在偏序下的传递性问题,findMostAdvanced在排序后仍会复核胜者。nil-aliastablet(未设置 alias 的候选)被排除在排序之外,避免空指针/无效排序参与主从切换决策。

备份恢复与 mysql 协议层

时间点恢复强制 UTC

mysqlctl: force TZ=UTC for mysqlbinlog during point-in-time restore (#20463):进行基于 binlog 的时间点恢复(PITR)时,mysqlbinlog进程强制以TZ=UTC运行。binlog 事件中的时间戳与本地时区无关,若宿主环境时区非 UTC,mysqlbinlog的时间过滤可能错位,导致恢复到错误的时间点。强制 UTC 消除了时区依赖,使恢复结果可复现、可预期。

流式错误不再表现为连接断开

  • go/mysql: streaming errors no longer surface as connection loss (#20383):流式查询过程中 MySQL 服务端返回的错误,此前可能被上层误解为连接丢失(connection loss)而触发重连/报错;修复后错误按正常流式错误传播,语义更清晰;
  • go/mysql: send ERR instead of teardown after an OK carrying SERVER_MORE_RESULTS_EXISTS (#20563):当响应以SERVER_MORE_RESULTS_EXISTS标志的 OK 包开始时(多结果集场景),若后续出现错误,客户端应发送 ERR 包而非直接关闭连接(teardown),保证多结果集协议交互的正确性。

MySQL JSON 编码修复

mysql/json: fix MarshalTo discarding accumulated output for nested blob and bit values (#20625)MarshalTo在编码嵌套的 blob 与 bit 类型值时,此前可能丢弃已累积的输出缓冲,导致 JSON 文档编码不完整,本次修复保证累加缓冲正确保留。

构建、依赖与安全加固

  • Go 版本升级:Docker 构建镜像中 Golang 升级到go1.25.12(#20519)与go1.25.13(#20835),引入工具链的 bug 修复与安全补丁;
  • CI 可靠性:修复 mysql57 CI 中已移除的libtinfo5固定版本问题并"快速失败"(#20481);修复query_serving_queries_2change-detection 过滤器的自引用(#20482);下载archive.ubuntu.com时逐个尝试已解析 IP(#20539);
  • CI 安全加固:GitHub context 通过环境变量而非模板展开传给脚本(#20784);收紧 workflow token 权限与 checkout 凭据(#20785);app token 作用域限定到单个 step 而非导出到GITHUB_ENV(#20786);
  • 文档与维护:移除内部未公开的 VRLog 功能(#20467);更新维护者与 code owner 项目列表(#20772);v23.0.6 代码冻结(#20992);
  • 测试基建:修复 topo-flavor e2e 分片"静默执行零测试"的问题(#20556),避免 CI 假绿。

升级建议与验证要点

从 v23.0.x 升级到 v23.0.6 时,建议重点关注以下行为变化并安排验证:

  1. VReplication/VDiff:涉及 SET 列(尤其达到 64 个成员上限)的表,升级后建议对在途的 MoveTables/Reshard 任务执行VDiff复核数据一致性;使用多列主键 + 过滤谓词的 VDiff 场景需回归验证。
  2. VTGate 表达式TO_BASE64输出(57 字节整数倍输入)、ROUND()浮点边界、ENUM 数值上下文的结果可能与旧版本不同,涉及这些表达式的应用可先做差异对比。
  3. 递归 CTE:流式递归 CTE 现在同样受 1000 层深度上限约束,超限会返回VT09030错误,应用侧需确保递归查询有合理终止条件。
  4. txthrottler:若使用target_replication_lag_sec=1,旧版本会 panic,升级后可安全启用该配置。
  5. 时间点恢复:PITR 现在强制 UTC 语义,跨时区环境的恢复行为更一致,但脚本若依赖本地时区时间过滤需同步调整。

总体而言,v23.0.6 是一次"质量沉淀"型补丁版本:没有新特性的大规模引入,而是把 VReplication 数据流、evalengine 兼容性、复制切换排序、协议错误语义等核心路径上的边界问题逐一收敛。对于生产环境用户,建议结合自身使用的功能模块,优先验证上表所列场景后完成升级。

【免费下载链接】vitessVitess is a database clustering system for horizontal scaling of MySQL.项目地址: https://gitcode.com/gh_mirrors/vi/vitess

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询