OceanBase源码解读:SQL优化器逻辑计划生成 ob_log_plan.cpp

在 OceanBase 的 SQL 编译链路中,解析与改写完成之后,优化器接手并开始生成可执行计划。ob_log_plan.cpp 中的 ObLogPlan 类正是这一阶段的核心:它负责把改写后的 AST 转换成逻辑算子树,并在此过程中完成多表连接的连接顺序枚举、基表路径生成、连接路径生成以及最终计划树装配。可以把它理解为优化器的“骨架”——ObOptimizer 决定什么时候开始优化,而 ObLogPlan 决定生成什么样的计划。

一、ObLogPlan 在优化器中的位置

SQL 优化器内部可粗略划分为三层:

  1. 逻辑改写层:基于启发式/基于代价的改写规则重写查询(如视图合并、子查询提升)。
  2. 逻辑计划生成层:由 ObLogPlan 负责,枚举 join order、生成 access/join path、组装逻辑算子。
  3. 物理计划生成层:由 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 为查询中的每一张基表创建类型为 ACCESSObJoinOrder,构成动态规划的初始层 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_DPjoin_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.c1t1.c1 = 1t2.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 还需要转换成 ObLogicalOperatorcreate_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 优化器从“怎么连”到“怎么执行”的桥梁。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注