资讯动态

TiDB FULL OUTER JOIN 支持解析:语法门控、规划器语义与四阶段执行落地路线

发布时间:2026/9/10 20:47:12 来源:尧图企业网站定制
TiDB FULL OUTER JOIN 支持解析语法门控、规划器语义与四阶段执行落地路线【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb本文以 docs/agents/executor/fullouter_join_dev_note.md 开发笔记为主体讲述 TiDB 为 SQL 标准FULL OUTER JOIN制定的设计决策与分阶段实现计划从仅能识别语法、拒绝执行开始逐步打通 Volcano 规划器路径与 root HashJoin v1 执行器最后再扩展至 TiFlash MPP shuffle join。读完本文你将理解 TiDB 为何用特性开关门控该语法、为何刻意不为FULL JOIN引入简写、规划器如何保证绝不静默退化为 inner join以及每一步落地时用户可以观察到的行为变化。一、目标与范围为 FULL OUTER JOIN 建立可演进的分阶段路线FULL OUTER JOIN全外连接会保留左右两侧全部输入既输出匹配到的行也输出左侧独有的行与右侧独有的行未匹配侧以 NULL 补齐。相比 TiDB 已支持的 left / right outer join它需要同时处理两侧的 NULL 扩展涉及语法、AST、规划器与执行器多个层面的联动变更因此被设计为一个分四步落地的功能对应跟踪 issue #69998增加语法、AST restore 输出与特性开关执行仍保持不支持增加 root 规划器Volcano 路径支持执行仍保持不支持增加 root HashJoin v1 执行器支持在 root 语义稳定后再增加 TiFlash MPP shuffle join 支持。首期目标In Scope支持FULL OUTER JOIN ... ON ...语法形式走 Volcano planner 路径非 Cascades planner由 root HashJoin v1 完成执行通过tidb_enable_full_outer_join特性开关门控默认OFF。首期明确不做Non-goalsFULL OUTER JOIN ... USING (...)与NATURAL FULL OUTER JOINCascades planner 支持root IndexJoin、MergeJoin、HashJoin v2 支持TiFlash MPP broadcast join 支持非 MPP 的 coprocessor join 下推。二、关键设计决策解读开发笔记用 Decision Log 记录了语法、特性开关、可空性、ON 条件处理与 Join Key 语义五个关键设计点这些决策直接决定了后面每一步的落地方式。2.1 语法决策只引入 FULL OUTER JOINFULL JOIN 不设简写设计上只引入FULL OUTER JOIN一种全连接语法不把FULL JOIN作为简写。原因在于FULL在当前语法中是一个非保留关键字unreserved keyword可以被用作别名或标识符。若把FULL JOIN纳入全连接语义会破坏与 MySQL 兼容的解析行为。例如下面的 SQLSELECT * FROM t1 full JOIN t2 ON t1.a t2.a;它应当继续被解析为t1 AS full JOIN t2把full当作 t1 的别名而不是一个 full outer join。这一决策在词法层有对应的特殊处理。由于FULL既可以是关键字又能作表别名若只依赖语法grammar规则t1 full outer join t2会先被规约成t1 AS full随后在OUTER处解析失败。为此 pkg/parser/lexer.go 在扫描到FULL时向后探测两个 token若恰好是OUTER JOIN就返回一个专用 tokenfullJoinType反过来刻意不在 lexer 与语法层把FULL JOIN处理成全连接简写。在 pkg/parser/parser.y 中可以看到fullJoinType对应的语法定义FULL OUTER JOIN。AST 侧的连接类型枚举位于 pkg/parser/ast/dml.go其中定义了CrossJoin / LeftJoin / RightJoin / FullJoin四种类型。AST restore把语法树还原为 SQL 文本路径则负责输出FULL OUTER关键字保证解析后再还原的 SQL 与原文等价——pkg/parser/parser_test.go 中就有还原一致性测试select * from t1 full outer join t2 on t1.a t2.a -- 还原为 SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.at2.a2.2 特性门控tidb_enable_full_outer_join 默认 OFF全外连接会同时改变多个 join 路径上的规划器与执行器行为因此在所有分阶段 PR 完成前默认关闭可以让语法先被认识、却不可被意外执行避免 root 语义尚未完备时被误用。在 pkg/sessionctx/vardef/tidb_vars.go 中注册了变量名tidb_enable_full_outer_join其默认值在 同文件 中定义为false。会话变量定义位于 pkg/sessionctx/variable/sysvar.go它是一个Global | Session双作用域布尔变量SetSession回调把字符串值写入SessionVars.EnableFullOuterJoin见 pkg/sessionctx/variable/session.go。对应地pkg/sessionctx/variable/sysvar_test.go 覆盖了该变量的默认值、on/0/1三种赋值方式的读写测试。2.3 禁止静默回退PlanBuilder 快速失败parser 识别出ast.FullJoin之后不允许在PlanBuilder中落入默认的 inner join 路径。在规划器与执行器尚未完全接通前最安全的行为是快速失败返回ErrNotSupportedYet(FULL OUTER JOIN)。当前仓库的 pkg/planner/core/logical_plan_builder.go 已经落地了这一 fail-fast 守卫其检查顺序非常清晰地体现了笔记中的全部限制条件特性开关未开启EnableFullOuterJoin false→ 直接报不支持开启 Cascades planner → 报FULL OUTER JOIN with cascades planner不支持全连接仅支持 Volcano 路径NATURAL JOIN、USING、缺少ON条件、以及 LATERAL 派生表场景 → 全部报不支持避免这些形式在后续路径中被悄悄转换成 inner apply / inner join。2.4 可空性Nullability两侧输出都视为可空全外连接同时保留两侧输入左右两边的子节点都可能产生 NULL 扩展行因此两侧输出列都必须被当作 nullable。这与 left / right outer join 不同——后者只有单侧会被 NULL 扩展。规划器一旦漏掉这一点NULL 扩展行上的谓词求值、投影与表达式推导都会出错。2.5 ON 条件处理一测谓词不允许无证明地下推全外连接不能复用单侧 outer join 的所有假设EqualConditions视作 join key 条件单侧ON谓词必须保留在 join 语义中除非后续有规则能证明某个变换是安全的依赖单一保留侧single preserved side的优化规则需要显式加入全连接的守卫。最核心的正确性风险是把某个保留侧的谓词意外下推到 join 之下在 NULL 扩展发生之前就过滤掉本该保留的行。2.6 Join Key 语义复用 / 与 IsNullEQ 机制root HashJoin v1 应复用现有 join key 的 null-safe 等值比较逻辑而不要为全连接引入一套全新的比较规则不匹配 NULL join keynull-safe equal允许NULL NULL匹配现有IsNullEQ机制按 key 表达行为。只有当现有IsNullEQ处理被证明不够用时才考虑引入仅全连接专用的比较规则。这一点在 parser 还原测试t1.a t2.a中也有体现。三、四步实现计划详解与用户可见行为Step 1语法与开关Syntax and Gate笔记标注该步骤已在首个 PR 中完成实现范围包括parser 增加FULL OUTER JOIN的 token/grammar 支持AST 增加 join 类型并支持 restore 输出保持FULL JOIN的别名兼容解析不作为全连接简写新增tidb_enable_full_outer_join默认OFF在PlanBuilder增加 fail-fast 守卫使FULL OUTER JOIN返回ErrNotSupportedYet而非落入 inner join补齐 parser、sysvar、planner-gate 测试。Step 1 之后的用户可见行为parser 能识别FULL OUTER JOINFULL JOIN仍按别名兼容语法解析执行FULL OUTER JOIN依旧返回 unsupported。这里有一个容易被误解的细节即便把开关设为ONStep 1 阶段执行仍会返回ErrNotSupportedYet。这是刻意为之——Step 1 只是让语法可被识别同时阻止它静默回退成别的 join 类型。Step 2root 规划器支持Root Planner Support规划器侧的落地内容包括增加逻辑连接类型FullOuterJoin。当前仓库中该类型已定义在 pkg/planner/core/base/plan_base.go与CrossJoin / InnerJoin / LeftOuterJoin / RightOuterJoin / SemiJoin / AntiJoin并列且IsOuterJoin一类判断会把全连接与单侧外连接归入同一族PlanBuilder仅在tidb_enable_full_outer_joinON时构建全外连接守卫代码见上文 2.3修复全连接的输出可空性守卫谓词下推、outer join simplification、join reorder、runtime filter 与物理计划枚举等规则物理计划范围限制为 root HashJoin v1增加临时 executor 构建守卫使正常执行仍返回ErrNotSupportedYet(FULL OUTER JOIN)增加逻辑/物理计划层面的 planner 测试。为什么 EXPLAIN 也需要走 planner 全路径笔记明确指出TiDB 的 explain executor 为了获取 partition pruning 元数据仍会构建目标 executor。因此在 Step 2用户对FULL OUTER JOIN ... ON ...执行EXPLAIN 同样返回 unsupported直到 Step 3 才移除 HashJoin v1 executor 的守卫。从当前仓库源码与测试的完成度看Step 2 的规划器语义已不只是计划中。逻辑连接类型FullOuterJoin已经存在全连接行数估算在 pkg/planner/cardinality/join.go 有EstimateFullJoinRowCount入口物理计划枚举在 pkg/planner/core/exhaust_physical_plans.go 也已有FullOuterJoin分支。更为系统的证据是 pkg/planner/core/casetest/fulljoin/full_join_test.go其中已包含大量规划器行为测试例如特性开关默认关闭TestFullOuterJoinFeatureSwitchDefaultOff逻辑计划正确构建为FullOuterJoinTestFullOuterJoinLogicalBuildUSING、NATURAL等不支持形式快速失败TestFullOuterJoinUnsupportedFormsFailFastCascades 路径快速失败TestFullOuterJoinCascadesFailFast物理计划仅落到 HashJoinTestFullOuterJoinPhysicalPlanHashJoinOnly使用非 HashJoin 的 join method hint 时给出警告TestFullOuterJoinUnsupportedJoinMethodHintsWarnouter join simplification 与 join reorder 不会破坏全连接语义TestFullOuterJoinSimplifyOuterJoin、TestFullOuterJoinSkipJoinReOrder。Step 2 之后的用户可见行为开启特性开关后planner 测试可以产出 root 全外连接计划执行或 EXPLAINFULL OUTER JOIN ... ON ...仍返回 unsupported不支持的形式与不支持的 planner 路径仍然快速失败。Step 3root HashJoin v1 执行器第一个可执行的实现应只做 root HashJoin v1把首个语义实现聚焦在正确性上需要逐一覆盖匹配行matched rows仅左侧未匹配行left-only unmatched rows仅右侧未匹配行right-only unmatched rows单侧ON过滤与两种 join key落盘行为spill。本步骤的落地内容还包括移除 Step 2 的临时 executor 构建守卫补充 executor 与集成测试。相关执行器代码位于 pkg/executor/join/hash_join_v1.go。HashJoin v1 的可空等值处理沿用现有IsNullEQ机制即 2.6 节所述不需要新增仅全连接专用的 join-key 比较规则。至于 root IndexJoin、MergeJoin、HashJoin v2必须等它们被单独设计与测试之后才能放开不会与全连接同时引入。Step 3 之后的用户可见行为开启特性开关后FULL OUTER JOIN ... ON ...在 root HashJoin v1 上可以正常工作不支持的形式与不支持的执行路径仍然快速失败。Step 4TiFlash MPP shuffle joinTiFlash MPP 支持应放在 root 语义稳定之后作为后续工作。首个 TiFlash 范围被限定为shuffle HashJoin允许全外连接的 MPP shuffle join不允许全外连接的 MPP broadcast join不在全外连接之上广告单侧 hash 分区属性需要安全的输出分区属性/NullEQjoin-key 下推保持不支持留作单独的兼容性跟进项。落地内容包括在 TiPBTiFlash 通信协议中编码 full outer join 类型、为全外连接选择安全的输出分区属性、补充 MPP planner 测试。Step 4 之后的用户可见行为条件满足时全外连接可以被规划为 TiFlash MPP shuffle joinbroadcast join 与NullEQjoin-key 下推仍作为独立的后续项跟进。四、进度追踪与规划器测试现状笔记中的进度追踪表完整记录如下StepScopeStatusStep 1Parser、AST restore、sysvar 门控、planner fail-fast 守卫DoneStep 2root 规划器语义与临时 executor unsupported 守卫PlannedStep 3root HashJoin v1 执行器支持PlannedStep 4TiFlash MPP shuffle full outer join 下推Planned结合当前仓库实际可见的代码与测试可以对上表做一处重要补充Step 2规划器侧的大部分工作已在仓库中落地——FullOuterJoin逻辑连接类型、PlanBuilder门控与限制检查、行数估算、物理计划枚举分支以及 pkg/planner/core/casetest/fulljoin/full_join_test.go 中成体系的 planner 测试均已存在而笔记标注的 Step 3 executor 行为与 Step 4 TiFlash MPP 下推按笔记记录仍处于后续计划状态。若读者想验证默认 OFF 全部 fail-fast这些不变量可直接运行pkg/planner/core/casetest/fulljoin目录下的规划器测试。五、评审清单合入前必须守住的不变量笔记在末尾给出了供 reviewer 使用的检查清单可以作为任何全外连接相关 PR 的验收标准FULL JOIN没有变成 full outer join 的简写FULL OUTER JOIN从不静默降级为 inner join特性开关默认值为OFF全连接语义启用后两侧输入都被视为可空单侧ON谓词不会被不安全地推到 join 之下root 执行从 HashJoin v1 开始未显式实现前不支持路径一律快速失败TiFlash MPP full outer join 只在 root 行为被充分覆盖后才引入。六、开发者如何本地验证当前仓库中该功能仍受特性开关门控若要自行验证解析与规划器行为需要注意限制条件开关默认关闭且 Step 3 执行器落地前即使开启也会得到 unsupported 错误。开启特性的方式按会话或全局SET SESSION tidb_enable_full_outer_join ON; -- 或 SET GLOBAL tidb_enable_full_outer_join ON;关闭与状态查询SET SESSION tidb_enable_full_outer_join OFF; SHOW VARIABLES LIKE tidb_enable_full_outer_join;可复现的语法检查用例对应 pkg/parser/parser_test.goSELECT * FROM t1 FULL OUTER JOIN t2 ON t1.a t2.a;而下面这条 SQL 由于FULL被当作别名不会构成全外连接SELECT * FROM t1 FULL JOIN t2 ON t1.a t2.a; -- 解析为 t1 AS full JOIN t2需要提醒的是在仓库当前的演进状态下对FULL OUTER JOIN ... ON ...的实际执行与 EXPLAIN 仍受笔记中分阶段计划的约束最终可执行能力以对应 step 是否落地为准。七、延伸阅读开发笔记原文语法与词法pkg/parser/lexer.go、pkg/parser/parser.y、pkg/parser/ast/dml.go特性开关pkg/sessionctx/variable/sysvar.go、pkg/sessionctx/vardef/tidb_vars.go、pkg/sessionctx/variable/session.go规划器守卫与连接类型pkg/planner/core/logical_plan_builder.go、pkg/planner/core/base/plan_base.go规划器测试pkg/planner/core/casetest/fulljoin/full_join_test.go执行器Step 3 相关pkg/executor/join/hash_join_v1.go【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价