OceanBase源码解读:SQL优化器入口 ob_optimizer.cpp

ob_optimizer.cpp 是 OceanBase SQL 优化器的“调度中枢”。它在 SQL 改写(rewrite)之后、代码生成(code_generator)之前被调用,职责是把经过 parser、resolver、rewrite 处理后的 ObDMLStmt 转换成一棵最优逻辑计划 ObLogPlan。与具体算子选择、连接重排等细节不同,这个文件更关注优化前的环境配置:并行策略、PDML、临时表、DAS、副本路由、统计模型等。理解它,就能看清一条 SQL 进入物理执行前必须走过的“政策审批”流程。

核心数据结构

  • ObOptimizerCtx ctx_:贯穿整个优化期的上下文,保存并行规则、开销、依赖表、临时表信息、各类特性开关。
  • ObLogPlan / ObSelectLogPlan:逻辑计划抽象。ObLogPlanFactory 根据语句类型创建具体子类,随后调用 generate_plan() 完成真正优化。
  • ObDMLStmt:经 parser/resolver/rewrite 后的语句抽象,是优化器的输入。
  • ObSqlTempTableInfo:WITH / CTE / 临时表的子计划容器,避免主查询中重复优化同一临时表。

关键流程一:optimize() 主入口

optimize() 是典型的“错误码瀑布”函数,任何一步失败都会通过 ret 级联返回。它的骨架如下:

int ObOptimizer::optimize(ObDMLStmt &stmt, ObLogPlan *&logical_plan)
{
  ACTIVE_SESSION_FLAG_SETTER_GUARD(in_sql_optimize);
  int ret = OB_SUCCESS;
  ObLogPlan *plan = NULL;
  ObDMLStmt *target_stmt = &stmt;
  ObTaskExecutorCtx *task_exec_ctx = ctx_.get_task_exec_ctx();
  if (stmt.is_explain_stmt()) {
    target_stmt = static_cast<ObExplainStmt*>(&stmt)->get_explain_query_stmt();
  }
  if (OB_ISNULL(query_ctx) || OB_ISNULL(session) ||
      OB_ISNULL(target_stmt)|| OB_ISNULL(task_exec_ctx)) {
    ret = OB_INVALID_ARGUMENT;
  } else if (OB_FAIL(init_env_info(*target_stmt))) {
  } else if (!target_stmt->is_reverse_link() &&
             OB_FAIL(generate_plan_for_temp_table(*target_stmt))) {
  } else if (OB_ISNULL(plan = ctx_.get_log_plan_factory().create(ctx_, stmt))) {
    ret = OB_ALLOCATE_MEMORY_FAILED;
  } else if (OB_FAIL(plan->generate_plan())) {
  } else if (OB_FAIL(plan->add_extra_dependency_table())) {
  }
  if (OB_SUCC(ret)) {
    logical_plan = plan;
  }
  return ret;
}

流程可以概括为:处理 EXPLAIN 语句 → 初始化环境 → 生成临时表子计划 → 创建具体 LogPlan → 生成逻辑计划 → 记录依赖表。其中 plan->generate_plan() 才是连接重排、算子选择、分布式策略的“主战场”。

关键流程二:init_env_info() 环境配置总线

优化器不是一上来就做计划,而是先回答“能不能并行?能不能 PDML?用哪类副本?”等问题。init_env_info() 按固定顺序串起这些决策:

int ObOptimizer::init_env_info(ObDMLStmt &stmt)
{
  int ret = OB_SUCCESS;
  if (OB_FAIL(extract_column_usage_info(stmt))) {
  } else if (OB_FAIL(extract_opt_ctx_basic_flags(stmt, *session_info))) {
  } else if (OB_FAIL(check_pdml_enabled(stmt, *session_info))) {
  } else if (OB_FAIL(check_direct_load_enabled(stmt, *session_info))) {
  } else if (OB_FAIL(check_parallel_das_dml_enabled(stmt, *session_info))) {
  } else if (OB_FAIL(check_dml_parallel_mode())) {
  } else if (OB_FAIL(init_parallel_policy(stmt, *session_info))) {
  } else if (OB_FAIL(init_replica_policy(stmt, *session_info))) {
  } else if (OB_FAIL(init_correlation_model(stmt, *session_info))) {
  } else if (OB_FAIL(init_table_access_policy(stmt, *session_info))) {
  } else if (OB_FAIL(check_enable_topn_runtime_filter())) {
  } else if (OB_FAIL(check_enable_runtime_filter_adaptive_apply())) {
  } else if (OB_FAIL(check_enable_delete_insert_scan())) {
  } else { /*do nothing*/ }
  return ret;
}

注意顺序:PDML 判定必须在 init_parallel_policy() 之前完成,因为并行策略会依赖 ctx_.can_use_pdml() 的结果。这种“先决策、后执行”的分层设计,让后续计划生成阶段可以专注于算子选择,而不必反复查询会话变量。

关键流程三:并行与 PDML 决策

init_parallel_policy() 确定并行执行规则(PXParallelRule)和并行度 DOP。其优先级从高到低为:

  1. 强制本地计划、PL UDF、cursor 表达式、dblink 等场景强制串行。
  2. PARALLEL hint 指定手动 DOP。
  3. PARALLEL(AUTO) hint 或会话变量开启 Auto DOP。
  4. outline 数据。
  5. 会话变量 _force_parallel_query_dop / _enable_parallel_query

如果未购买 OLAP 许可证,高并行度会被降级为 LICENSE_NOT_ALLOW_OLAP,这是 OceanBase 企业版特性授权的体现。

PDML 的判定集中在 check_pdml_enabled(),决策链为 hint → auto DOP → 会话变量。PDML 能大幅提升批量写入吞吐,但受严格模式、唯一索引、触发器、外键、用户嵌套 SQL 等限制,不满足条件时会记录 PlanNote 供 EXPLAIN 展示。

关键流程四:临时表子计划

generate_plan_for_temp_table() 负责处理 WITH / CTE。它先收集语句中所有临时表,再为每个临时表查询创建 ObSelectLogPlan,下推可下推的 filter,生成 raw plan,选择副本,最后把最优算子保存到 ObSqlTempTableInfo。主查询在后续优化中直接引用这些预先生成的子计划,避免重复展开和优化。

小结

ob_optimizer.cpp 本身不直接选择 join 顺序或索引,但它定义了“在什么条件下允许做什么”的全部政策。它把 parser/resolver/rewrite 之后得到的语句,先完成并行、PDML、临时表、副本等环境初始化,再交给 ObLogPlan 做真正的逻辑计划生成。对 OceanBase 源码阅读者来说,这里是理解“SQL 如何被允许并行执行、何时退化为串行、如何处理 CTE”的最佳入口。

发表回复

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