在 OceanBase 的 SQL 编译链路中,解析与改写完成之后,优化器接手并开始生成可执行计划。ob_log_plan.cpp 中的 ObLogPlan 类正是这一阶段的核心:它负责把改写后的 AST 转换成逻辑算子树,并在此过程中完成多表连接的连接顺序枚举、基表路径生成、连接路径生成以及最终计划树装配。可以把它理解为优化器的“骨架”——ObOptimizer 决定什么时候开始优化,而 ObLogPlan 决定生成什么样的计划。
一、ObLogPlan 在优化器中的位置
SQL 优化器内部可粗略划分为三层:
- 逻辑改写层:基于启发式/基于代价的改写规则重写查询(如视图合并、子查询提升)。
- 逻辑计划生成层:由
ObLogPlan负责,枚举 join order、生成 access/join path、组装逻辑算子。 - 物理计划生成层:由 code generator 把逻辑算子树转成物理执行算子。
ObLogPlan 的输入是 ObDMLStmt,输出是 ObLogicalOperator 树根节点。它内部大量使用 ObJoinOrder 作为动态规划子问题,Path 作为候选路径抽象。
二、核心数据结构
- ObJoinOrder:表示一组表的连接子问题,保存该子问题的 interesting paths 与 conflict detectors。单表时是 ACCESS 类型,多表连接时是 JOIN 类型。
- JoinOrderArray /
ObIArray<JoinOrderArray>join_rels:动态规划表。join_rels[level]表示包含level+1张表的所有 join order 候选。 - ObConflictDetector:连接谓词与表集合的冱突检测器。它决定两个子树之间是否存在合法连接条件,是避免非法笛卡尔积与实现连接下推的关键。
- Path / AccessPath / JoinPath:逻辑/物理路径抽象。
AccessPath描述扫描方式(主键、索引、全表),JoinPath描述 NLJ/HJ/MJ 等连接方式。最终通过create_plan_tree_from_path转成算子。
三、连接顺序枚举主流程
主入口 generate_join_orders 把优化过程拆成四个阶段:收集表与谓词、预处理谓词、生成基表路径、使用 IDP 动态规划枚举连接顺序。
int ObLogPlan::generate_join_orders()
{
// 1. 收集 from tables / base tables / where 条件 / 下推过滤
get_from_tables(from_table_items);
get_base_table_items(stmt, base_table_items);
append(quals, stmt->get_condition_exprs());
append(quals, get_pushdown_filters());
// 2. 谓词预处理:子查询、OR 拆分、常量过滤、startup filter 等
pre_process_quals(from_table_items, stmt->get_semi_infos(), quals);
// 3. 生成基表 join order,并为每张表生成 base path
generate_base_level_join_order(base_table_items, base_level);
for (每个基表) {
join_rels.at(0).at(i)->generate_base_paths();
}
// 4. IDP 动态规划枚举所有合法 join 顺序
generate_join_levels_with_IDP(join_rels);
join_order_ = join_rels.at(join_level - 1).at(0);
}
3.1 基表初始化
generate_base_level_join_order 为查询中的每一张基表创建类型为 ACCESS 的 ObJoinOrder,构成动态规划的初始层 join_rels[0]。
int ObLogPlan::generate_base_level_join_order(
ObJoinOrder*> &base_level)
{
for (int64_t i = 0; OB_SUCC(ret) && i init_base_join_order(table_items.at(i));
base_level.push_back(this_jo);
}
return ret;
}
3.2 谓词预处理
pre_process_quals 把 where 条件按类型分流:含子查询的谓词、含 OR 的谓词、常量表达式、非确定性表达式、层级查询表达式等,分别交给不同的后续模块处理。
int ObLogPlan::pre_process_quals(...)
{
for (每 qual) {
if (qual->has_flag(CNT_SUB_QUERY)) {
// 子查询过滤:部分可下推,部分保留为 subplan filter
} else if (qual->is_const_expr()) {
// 常量过滤:静态 false 保留,其余作为 startup filter
} else if (!qual->is_deterministic()) {
// 非确定性表达式特殊处理
} else {
normal_quals.push_back(qual);
}
}
// 继续处理 on 条件与 semi/anti join 条件
}
3.3 动态规划组合子问题
generate_single_join_level_with_DP 用 join_rels[left_level] 与 join_rels[right_level] 中的子问题两两组合,生成更高层的 join order。优先枚举存在连接条件的组合,避免笛卡尔积爆炸,并检查 leading hint 是否冲突。
int ObLogPlan::generate_single_join_level_with_DP(
ObIArray &join_rels,
uint32_t left_level, uint32_t right_level,
uint32_t level, bool ignore_hint, ObIDPAbortType &abort_type)
{
for (left in join_rels[left_level]) {
for (right in join_rels[right_level]) {
check_join_hint(left->get_tables(), right->get_tables(),
match_hint, is_legal, is_strict_order);
if (!is_legal) continue;
inner_generate_join_order(join_rels, left, right, level,
match_hint, !match_hint,
is_valid_join, join_tree);
}
}
}
3.4 两个子问题的原子组合
inner_generate_join_order 是组合左右子树的核心:检查表集合是否重叠、查找或创建当前 ObJoinOrder、选择合法 conflict detectors、处理连接谓词、生成 join paths。
int ObLogPlan::inner_generate_join_order(
ObIArray &join_rels,
ObJoinOrder *left_tree, ObJoinOrder *right_tree,
uint32_t level, bool hint_force_order,
bool delay_cross_product, bool &is_valid_join,
ObJoinOrder *&join_tree)
{
if (left_tree->get_tables().overlap(right_tree->get_tables())) {
// 非法连接:左右子树包含同一张表
} else if (OB_FAIL(find_join_rel(cur_relids, join_tree))) {
// 查找是否已有相同表集合的 join order
} else if (OB_FAIL(ObConflictDetector::choose_detectors(
left_tree->get_tables(), right_tree->get_tables(), ...))) {
// 选择合法连接谓词
} else if (OB_FAIL(process_join_pred(left_tree, right_tree, join_info))) {
// 去冗余并提取等值连接
} else if (OB_FAIL(join_tree->generate_join_paths(
*left_tree, *right_tree, join_info, hint_force_order))) {
// 生成 NLJ/HJ/MJ 路径候选
}
}
3.5 连接谓词精简
process_join_pred 根据等价类移除冗余等值条件。例如,当 t1.c1 = t2.c1 与 t1.c1 = 1、t2.c1 = 1 同时存在时,等值连接条件可被推导为恒真,从而被移除。
int ObLogPlan::process_join_pred(
ObJoinOrder *left_tree,
ObJoinOrder *right_tree,
JoinInfo &join_info)
{
if (INNER_JOIN == join_info.join_type_) {
// 1. 移除基于等价类的冗余等值谓词
// 2. 保留真正连接左右两个子树的谓词
// 3. 为 hash/merge join 提取等值对
}
}
四、从路径到逻辑算子树
动态规划完成后,join_order_ 中的最优 Path 还需要转换成 ObLogicalOperator。create_plan_tree_from_path 递归处理 access/join/subquery 等路径类型,分配对应的逻辑算子,并附加子查询过滤、参数约束等元信息。
int ObLogPlan::create_plan_tree_from_path(
Path *path, ObLogicalOperator *&out_plan_tree)
{
if (path->is_access_path()) {
allocate_access_path(access_path, op);
} else if (path->is_join_path()) {
allocate_join_path(join_path, op);
} else if (path->is_subquery_path()) {
allocate_subquery_path(subquery_path, op);
}
// 同一 Path 只分配一次算子,结果缓存在 path->log_op_
path->log_op_ = op;
out_plan_tree = op;
}
五、小结
ob_log_plan.cpp 承载了 OceanBase 优化器从“语句结构”到“逻辑算子树”的核心转换。它的设计要点可以概括为:
- 动态规划表:
join_rels[level] 保存所有包含固定数量表的子ᗮ题,避免重复计算。
- 冲突检测器:
ObConflictDetector 决定哪些子树可以合法连接,是连接顺序枚举的安全边界。
- 路径抽象:
Path 体系把逻辑计划与物理实现解耦,使得同一份逻辑计划可以对应多种物理执行策略。
- 递归装配:
create_plan_tree_from_path 把最优路径自下而上转成 ObLogicalOperator,交给后续 code generator。
理解 ObLogPlan 的工作方式,就把握了 OceanBase 优化器从“怎么连”到“怎么执行”的桥梁。