- 数据库
- 分布式数据库
- 云原生
- 后端
- 数据存储
【免费下载链接】vitess
Vitess is a database clustering system for horizontal scaling of MySQL.
导读
本文基于 Vitess 官方仓库 changelog/14.0/14.0.3/changelog.md 及其配套的 release_notes.md、summary.md,系统梳理 v14.0.3 这一补丁版本的全部变更内容。v14.0.3 是 Vitess 14.0 系列的第 3 个补丁版本,共包含 12 个提交(不含合并提交),聚焦于查询服务(Query Serving)的正确性修复、VReplication 的边界情况处理、VTOrc 高可用发现机制的缺陷修复,以及发布流程的工程化改进。阅读本文后,你将掌握该版本每个修复的触发场景、底层实现原理与对应源码位置,并能据此判断升级到 v14.0.3 前需要关注的已知问题与兼容性前提。
版本概览:一次面向稳定性的补丁发布
v14.0.3 属于 Vitess 14.0 系列的补丁(patch)发布。从仓库 changelog/14.0/README.md 可以看出,14.0 系列共发布到 14.0.5,而 v14.0.3 介于 v14.0.2 与 v14.0.4 之间。该版本包含 12 个提交(不含合并提交),贡献者包括@GuptaManan100、@frouioui、@harshit-gangal、@mattlord与@vitess-bot。
整个变更清单分为两大板块:
| 板块 | 子类 | 变更数 | 涉及模块 |
|---|---|---|---|
| Bug fixes | Query Serving | 3 | vtgate 查询规划与执行引擎 |
| Bug fixes | VReplication | 1 | vreplication 数据流复制 |
| Bug fixes | VTorc | 1 | vtorc 高可用巡检与恢复 |
| Release | Documentation | 3 | 发布文档与 changelog |
| Release | General | 3 | 发布脚本与工作流改进 |
下文先交代本版本最重要的已知问题与核心变更,再逐一深入每个 Bug 修复的实现细节,最后解读发布流程的工程改进。
已知问题:非 FULL GROUP BY 查询 + JOIN 的结果损坏
release notes 中明确列出了一个已知问题(release_notes.md):
- 非 full-group-by 查询与 JOIN 组合时可能产生损坏的结果。当 SQL 查询同时包含 JOIN 且未使用
FULL GROUP BY语义(即 MySQL 的ONLY_FULL_GROUP_BY模式下非法的聚合写法)时,vtgate 返回的结果可能不正确。
该问题的规避方式是在查询中显式使用 full-group-by 写法(即所有非聚合列都出现在GROUP BY子句中,或使用ANY_VALUE等 MySQL 提供的机制)。这一点对计划升级到 v14.0.3 的团队是重要的验收前提:升级后应重点回归检查生产环境中的聚合 + JOIN 类查询,确保不存在依赖 MySQL 宽松分组语义的 SQL。
核心变更:VTOrc 发现机制的持久性修复
问题背景:一次失败的发现导致永久失联
VTOrc 是 Vitess 集群中的高可用(HA)管理与恢复组件,负责持续巡检所有 tablet 对应的 MySQL 实例,并在主库故障时触发主从切换(reparenting)。VTOrc 的正常工作依赖一个"发现(discovery)"流程:它从拓扑服务(topo)读取 tablet 列表,再逐个连接 tablet 的 MySQL 实例读取实例状态,并将结果写入自身的后端数据库(SQLite)。
在 v14.0.3 之前的补丁版本中,存在一个严重的缺陷:一旦 VTOrc 无法连通某个 vttablet 对应的 MySQL 实例,它之后永远不会再尝试重新发现该 tablet。也就是说,一次瞬时故障(网络抖动、MySQL 重启、Pod 被驱逐)就会让该 tablet 在 VTOrc 的视野中永久"消失",无法再被巡检,也就失去了被故障恢复保护的能力。
此前唯一的临时解决办法是重启 VTOrc,让其重新执行一次全量发现。但在 Kubernetes 环境中,Pod 驱逐(eviction)频繁发生——Pod 被驱逐后在另一节点重新调度,其网络地址或连通状态发生变化,VTOrc 一旦在错误时机发现失败,就会与这些"搬家"后的 tablet 永久失联,运维上极难根治。
修复方案:对不在 database_instance 表中的 tablet 持续重试
该缺陷由 PR #10662 修复,修复思路是:VTOrc 不仅要发现已记录在案(database_instance表)的实例,还要周期性重试发现那些当前不在该表中的 tablet。
结合源码可以更清晰地理解这一机制。VTOrc 的发现与刷新流程集中在 go/vt/vtorc/logic/tablet_discovery.go:
OpenTabletDiscovery(第 233 行)打开拓扑服务连接,启动时先清空vitess_tablet缓存表(DELETE FROM vitess_tablet),随后调用refreshAllInformation做一次全量刷新,并返回一个基于GetTopoInformationRefreshDuration的定时器,周期性触发刷新。refreshAllTablets(第 291 行)通过refreshTabletsUsing逐 cell 从 topo 读取 tablet 列表,对每个 tablet 调用DiscoverInstance(tabletAlias, false)。refreshTablets(第 381 行)在保存 tablet 记录后,把已从 topo 中消失的 tablet(存在于vitess_tablet表但不在最新 topo 列表中的 alias)通过inst.ForgetInstance从database_instance表中清除。
关键点在于discover与forget的联动:旧实现中,当某 tablet 的 MySQL 连接失败时,该实例的相关记录会被标记为不可发现(不再进入发现队列),导致后续刷新周期中即使 topo 里还有该 tablet,VTOrc 也会跳过它。PR #10662 的改动使发现逻辑不再以"曾经发现失败"作为永久排除依据,而是持续把 topo 中存在的 tablet(无论其是否已存在于database_instance表)送入发现队列重试。
database_instance表是 VTOrc 后端数据库中记录每个 MySQL 实例健康信息与复制拓扑的核心表,其读写集中在 go/vt/vtorc/inst/instance_dao.go(例如第 1047 行的mkInsert("database_instance", ...)批量写入、第 1178-1180 行的实例删除逻辑)。修复后,VTOrc 的周期性刷新会:
- 对 topo 中新增或变更的 tablet:立即发现并写入
database_instance; - 对 topo 中存在但发现失败的 tablet:保留在发现队列中,下个周期继续重试,而不是永久放弃;
- 对 topo 中已删除的 tablet:走
ForgetInstance清理流程。
从架构层面看,这一修复让 VTOrc 的发现行为从"一次失败、永久失联、靠重启自救"转变为"持续重试、自愈收敛",对 Pod 频繁迁移的 Kubernetes 部署尤为关键。
相关配置项
VTOrc 的发现与巡检行为受配置控制。仓库中的默认配置位于 config/vtorc/default.json:
{ "Debug": true, "RecoveryPeriodBlockSeconds": 5 }在真实部署中,可通过 JSON 配置或命令行标志调整巡检周期等参数,相关取值定义与 getter 实现在 go/vt/vtorc/config/config.go(如GetInstancePollSeconds、GetTopoInformationRefreshDuration)。此外,VTOrc 支持通过--clusters-to-watch指定监控的 keyspace/分片范围,以及--cells-no-recovery指定跳过恢复动作的 cell(注册逻辑见 tablet_discovery.go),这些标志与本次发现修复共同决定了 VTOrc 的实际巡检覆盖面。
查询服务(Query Serving)修复详解
v14.0.3 在查询服务模块修复了 3 个问题,全部与 vtgate 的查询规划(planning)或执行正确性相关。
修复一:vtgate 上排序时也进行列截断(#11324)
触发场景:当查询在 vtgate 层执行内存排序(即排序键无法下推给 MySQL,需要在 vtgate 聚合排序)时,如果结果集列数超出预期,此前可能出现多余的列未被截断的问题。
实现证据:vtgate 的内存排序器位于 go/vt/vtgate/engine/memory_sort.go。该结构体定义了TruncateColumnCount字段(第 41-44 行),用于"指定要返回的列数":
// TruncateColumnCount specifies the number of columns to return TruncateColumnCount int在Execute与流式执行路径中,内存排序完成后都会调用result.Truncate(ms.TruncateColumnCount)(第 65 行、第 78 行)对结果做列截断。该修复确保"即使结果需要经过 vtgate 内存排序,列截断依然生效",从而保证返回给客户端的列数与查询语义一致。配套的单测位于 go/vt/vtgate/engine/memory_sort_test.go(第 444 行附近有TruncateColumnCount: 2的用例)。
修复二:LEFT JOIN 中复杂谓词被错误拉入 ON 条件(#11333)
触发场景:对于LEFT JOIN语句,规划器在处理复杂谓词(compound predicates,即由 AND/OR 等组合而成的多条件表达式)时,可能错误地将本应留在WHERE子句中的谓词上推(pull-up)进ON条件。
为何危险:LEFT JOIN的语义中,ON与WHERE对右表空值行的过滤行为完全不同——WHERE条件可以过滤掉因左连接产生的 NULL 扩展行,而ON条件不能。把谓词从WHERE错误挪到ON,会直接导致结果集中多出本应被过滤的行,属于典型的结果正确性缺陷。
实现证据:vtgate 规划器中与 JOIN 合并相关的代码在 go/vt/vtgate/planbuilder/operators/apply_join.go(第 351 行出现LEFT JOIN的拼接逻辑)与 go/vt/vtgate/planbuilder/operators/join_merging.go(第 40 行注释明确提到"如果左侧是 dual 且为 left join……只能合并到单分片路由")。该修复约束了谓词上推的适用条件:只有语义等价的简单谓词才允许被移入ON,复杂谓词则保留在原位置,从而杜绝上述结果偏差。
修复三:DML 引擎对 multiequal 的支持(#11395)
触发场景:DML(INSERT/UPDATE/DELETE)语句在执行路由(routing)规划时,如果主键(vindex)列以多值等值(multiequal)形式出现,此前 DML 引擎可能无法正确识别与路由。
实现证据:vtgate 的执行引擎在 go/vt/vtgate/engine/delete.go 第 58 行的case分支中明确处理了Equal, IN, Scatter, ByDestination, SubShard, EqualUnique, MultiEqual等多种路由 opcode;配套测试 go/vt/vtgate/engine/delete_test.go 中的TestDeleteMultiEqual直接覆盖了Opcode: MultiEqual的 DELETE 场景。同时,规划器在 go/vt/vtgate/planbuilder/operators/sharded_routing.go 中为分片路由生成engine.MultiEqualopcode(第 374、590、624 行),并且 dml_cases.json 中存在大量"Variant": "MultiEqual"的规划快照用例。该修复补齐了 DML 执行路径上对 multiequal 路由的完整支持,确保多值等值条件的 DML 能正确下推与执行。
VReplication 修复:DECIMAL 0 值边界情况(#11232)
触发场景:VReplication 在处理行事件时,若某列类型为 DECIMAL 且值为 0(包括0.00、0.000等不同精度形式),旧的实现可能在该边界值上处理错误,导致复制数据与源库不一致。
相关代码背景:VReplication 的核心实现位于 go/vt/vttablet/tabletmanager/vreplication,其中vcopier.go负责全量拷贝阶段的表数据复制(第 290 行附近注释描述了"复制器决定是否再次调用 copyNext"的控制逻辑),engine.go负责增量事件消费。DECIMAL 是 MySQL 二进制协议中的可变长度类型,其编码(尤其是值为 0 时的符号位、缩放因子组合)存在多种合法形态,这正是此类"0 值边界情况"容易出错的根源。该修复确保 DECIMAL 0 值在行事件中能被正确解析与重放,避免主从数据不一致。
对于依赖 VReplication(MoveTables、Reshard、Materialize、OnlineDDL 等)的用户,v14.0.3 值得重点关注:涉及 DECIMAL 列且存在 0 值的表,在升级后应通过 vdiff 等方式验证复制一致性。
发布流程改进:从单 PR 到双 PR 的工程化
v14.0.3 的变更还包含发布(Release)流程本身的工程化改进,这部分虽不直接面向运行时,但对理解 Vitess 的版本管理方式很有价值。
代码冻结脚本与工作流(#11198)
引入了一个简单化的代码冻结(code freeze)脚本与配套工作流。仓库中的实现为 tools/code_freeze.sh:脚本接受两个参数——freeze或unfreeze,以及目标分支名。它基于目标分支创建一个-code-freeze-N的新分支,然后修改.github/workflows/code_freeze.yml中的退出码:
freeze时把所有exit替换为exit 1,使 CI 工作流必然失败,从而阻止向冻结分支合入新代码;unfreeze时恢复为exit 0,使工作流必然成功,解除冻结。
随后将改动提交并针对目标分支创建 Pull Request。这套机制让维护者可以在发布窗口内以"提交即开关"的方式控制分支合并门槛,避免了人工口头约定。
发布脚本拆分:一次发布两个 PR(#11230)
改进发布辅助脚本do_release,使一次发布操作生成两个独立的 Pull Request而非合并为一个。从工程实践看,这种拆分通常是为了区分"版本号 / changelog 等元数据更新"与"源码 / 配置变更"两类改动,从而让评审者能够分别审查、分别合入,降低发布变更的耦合风险。发布相关辅助逻辑集中在 tools/create_release.sh 与 tools/release_utils.sh。
文档与链接改进(#11174、#11241、#11396)
- 更新发布文档(#11174),使发布步骤说明与新的双 PR 流程保持一致;
- 在发布 changelog 中为条目补充超链接(#11241),提升可追溯性;
- 为 v14.0.3 增加发布摘要(#11396),即本仓库中 summary.md 的来源。
升级与验证建议
综合以上变更,对计划部署 v14.0.3 的团队给出如下建议:
- 回归聚合 + JOIN 查询:受已知问题影响,先排查是否存在依赖非 full-group-by 语义的查询,并确认其结果正确性;
- 验证 VReplication 一致性:对含 DECIMAL 0 值的表执行 vdiff,确认复制数据一致;
- 观察 VTOrc 巡检覆盖:升级后检查 VTOrc 日志与
database_instance表记录,确认此前"失联"的 tablet 已被重新发现,Kubernetes 环境下重点观察 Pod 驱逐后新 tablet 能否被自动纳入巡检; - 检查 DML 路由:对多值等值条件的 UPDATE/DELETE 语句做针对性冒烟测试,确认路由与执行正确。
如需回顾本版本的完整变更清单,可查阅仓库内的 changelog.md、release_notes.md 与 summary.md,并对照 changelog/14.0/README.md 了解整个 14.0 系列的发布脉络。
- 数据库
- 分布式数据库
- 云原生
- 后端
- 数据存储
【免费下载链接】vitess
Vitess is a database clustering system for horizontal scaling of MySQL.
相关推荐
Vitess v15.0.5 补丁版本发布详解:Online DDL、查询服务与半同步修复深度解析
Vitess v15.0.5 补丁版本发布详解:Online DDL、查询服务与半同步修复深度解析 v15.0.5 是 Vitess 15.0 系列的一个维护补
数据库分布式数据库云原生后端数据存储copyparty 界面美化四步搞定:自定义主题完整指南
copyparty 界面美化四步搞定:自定义主题完整指南 copyparty(单文件 Python 文件服务器)默认的灰黑界面没问题,就是看久了有点寡淡。这篇拿
数据库分布式数据库云原生后端数据存储Sunshine游戏串流服务器实战:3条路线装好它,从配对到远程畅玩
Sunshine游戏串流服务器实战:3条路线装好它,从配对到远程畅玩 Sunshine 是一款完全开源免费的自托管游戏串流服务器,专门配合 Moonlight
数据库分布式数据库云原生后端数据存储
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考