SQL Vitess 的 VTGate / VTTablet / VTCoord 的分层架构与查询路由实践

1次阅读

vtgate通过分片键匹配路由sql,未命中则广播;vttablet依赖vitess_schema同步表结构;vtcoord批量写入非原子;sql重写会丢失别名。

SQL Vitess 的 VTGate / VTTablet / VTCoord 的分层架构与查询路由实践

VTGate 怎么把 SQL 请求分发到正确的分片?

VTGate 不解析 SQL 语义,只做轻量级路由判断。它依赖表的 vitess_schema 元信息(比如 sharding_key 字段定义)和查询中的 WHERE 条件,匹配预定义的分片规则(如 hashrange)。没命中分片键的查询(比如 select * FROM user WHERE status = 'active'),默认广播到所有分片。

  • 分片键必须出现在 WHEREIN 子句中,且不能被函数包裹(WHERE YEAR(created_at) = 2024 就不识别)
  • JOIN 跨分片表时,VTGate 会退化为“主表驱动 + 关联表广播”,性能陡降,实际应避免
  • 如果用 IN,值数量超过 queryserver-config-max-in-values(默认 1000),VTGate 会拒绝执行并报错 in list too large

VTTablet 启动后查不到表结构?

VTTablet 本身不托管 DDL,它只加载本地 mysql 实例已有的表结构。如果执行了 ALTER TABLE 但没同步更新 vitess_schema 中的 table_info,VTTablet 会认为该表“不存在”,导致 VTGate 路由失败或返回空结果。

  • 必须通过 ApplySchema 命令更新全局 schema:
    vitessctl ApplySchema --keyspace=commerce --sql="ALTER TABLE user ADD column tags JSON"
  • 直接在底层 MySQL 执行 DDL 后,VTTablet 日志会出现 schema mismatch 警告,但不会自动重载
  • 多个分片的表结构不一致时,VTTablet 不报错,但 VTGate 可能因字段缺失抛出 column not found

为什么 VTCoord 的 ExecuteBatch 返回部分成功?

VTCoord 是 VTTablet 内部协调组件,负责处理批量写入。它默认不开启事务性批量(即不是原子的 START TRANSACTION; ... COMMIT),而是对每个 INSERT/UPDATE 单独提交。只要某一分片写入失败(比如唯一键冲突、主键不存在),其余分片仍会提交成功。

  • 错误类型是 VtTabletError,常见错误码:2002(连接失败)、1062(唯一键冲突)、1364(字段无默认值)
  • 没有跨分片回滚机制,应用层需自行实现补偿逻辑(比如记录失败 ID 后异步重试)
  • 若需强一致性,应改用单分片事务 + Begin/Commit,或启用 TwoPc(但会显著增加延迟和锁竞争)

SQL 被重写后字段别名丢失?

VTGate 在路由前会对 SQL 做语法树重写(例如把 SELECT id FROM user 改成 SELECT user.id FROM user 以适配分片表名),这个过程会抹掉原始别名。尤其在 GROUP BYORDER BY 引用别名时,会报错 Unknown column 'xxx' in 'order clause'

  • 别名必须显式重复在 ORDER BY 中,不能依赖 SELECT 里的定义
    ✅ 正确:SELECT u.id AS uid FROM user u ORDER BY u.id
    ❌ 错误:SELECT u.id AS uid FROM user u ORDER BY uid
  • 使用 union 时,VTGate 会统一列名,但各子查询的别名不继承,建议用位置序号(ORDER BY 1)代替
  • JSON_EXTRACT 等函数返回的匿名字段,在重写后更易丢失上下文,建议始终用 AS 显式命名

分片键设计不合理、schema 同步滞后、以及过度依赖 SQL 别名——这三处最容易在上线后突然暴露,而且日志里往往只报错不报因。

text=ZqhQzanResources