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。其优先级从高到低为:
- 强制本地计划、PL UDF、cursor 表达式、dblink 等场景强制串行。
PARALLELhint 指定手动 DOP。PARALLEL(AUTO)hint 或会话变量开启 Auto DOP。- outline 数据。
- 会话变量
_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”的最佳入口。