OceanBase源码解读:物化视图刷新事务封装 ob_mview_transactio

在 OceanBase 4.3 的物化视图(Materialized View, MV)实现里,刷新动作并不是简单地在用户会话里执行一条 INSERT SELECT。刷新需要走内部 SPI 连接、可能要切换默认数据库、还要兼容 Oracle/MySQL 两种模式,因此必须先把用户会话的“环境”妥善保存,再在独立的事务通道里执行内部 SQL,最后把现场还原。ob_mview_transaction.cpp 就是承担这套“保存-执行-恢复”工作的轻量事务封装层。

核心数据结构

文件里主要出现四类对象,按职责由下往上堆叠:

  • ObSessionParamSaved:保存/恢复会话参数。包括 autocommit、当前 database_id/database_name、inner/user 身份、DDL 刷新标记,以及 MV 创建时“固化”下来的系统变量差异。
  • ObSessionSavedForInner:保存/恢复语句级会话状态。它把整份 StmtSavedValue 快照下来,并把默认库临时切到 oceanbase(sys 库),以便内部元数据 SQL 执行。
  • ObMViewTransaction:事务外壳。负责从 ObISQLClient 获取内部连接、显式 start/commit/rollback,并在失败时按镜像顺序清理。
  • ObMViewTransactionInnerMySQLGuard:RAII 守卫。在 Oracle 租户场景下,临时把兼容模式切到 MySQL_MODE,保证内部字典 SQL 按 MySQL 语法解析;析构时自动恢复旧模式。

关键流程:保存会话参数

ObSessionParamSaved::save 是进入 MV 刷新上下文的第一件事:先把 autocommit 关掉、把会话标记为 inner session、设置 refreshing_mview 的 DDL 标记,再把 MV 固化变量里与用户当前会话不同的系统变量覆盖进去。

// 关键代码片段(save 函数骨架)
int ObMViewTransaction::ObSessionParamSaved::save(
    ObSQLSessionInfo *session_info,
    const sql::ObLocalSessionVar *mv_solidified_session_var)
{
  ...
  // 保存 autocommit / database_name / database_id
  session_info_ = session_info;
  is_inner_ = session_info->is_inner();
  autocommit_ = autocommit;
  database_id_ = session_info->get_database_id();

  // 强制切换到 inner session 并关闭自动提交
  session_info->set_inner_session();
  session_info->set_autocommit(false);

  // 设置 DDL 标记:当前正在刷新 MV
  InnerDDLInfo ddl_info;
  ddl_info.set_refreshing_mview(true);
  session_info->get_ddl_info().init(ddl_info, 0);

  // 若存在固化变量差异,逐条覆盖当前会话系统变量
  ARRAY_FOREACH(mv_solidified_diff_vars, i) {
    session_info_->update_sys_variable(var->type_, var->val_);
  }
  ...
}

这里的设计动机很明确:MV 刷新是后台执行的内核动作,不能继承用户会话的 autocommit 状态,也不能让外部会话意外看到中间状态。

关键流程:切到 sys 库执行内部 SQL

ObSessionSavedForInner 解决的是“内部 SQL 该用哪个库”的问题。MV 刷新时常常要查内部字典或走 sys 租户逻辑,因此它把整份会话状态快照,再把默认库换成 oceanbase。

// 关键代码片段(save 与 restore)
int ObMViewTransaction::ObSessionSavedForInner::save(
    ObSQLSessionInfo *session_info)
{
  // 1. 快照整个 StmtSavedValue
  session_info->save_session(*session_saved_value);
  session_saved_value_ = session_saved_value;

  // 2. 把默认库切到 sys 库
  session_info->set_default_database(OB_SYS_DATABASE_NAME);
  session_info->set_database_id(OB_SYS_DATABASE_ID);
}

int ObMViewTransaction::ObSessionSavedForInner::restore()
{
  // 1. 恢复整份 StmtSavedValue
  session_info_->restore_session(*session_saved_value_);
  session_saved_value_->~StmtSavedValue();
  allocator_.free(session_saved_value_);

  // 2. 恢复原来的默认库
  session_info_->set_default_database(database_name_);
  session_info_->set_database_id(database_id_);
}

注意 restore 里显式调用了 ~StmtSavedValue() 再 free,因为这份快照是从 allocator 里 new 出来的,不能简单释放内存而不执行析构。

关键流程:事务外壳 start / end

ObMViewTransaction::start 把前述两步串成标准入口:保存会话参数 → 设置目标库 → 获取内部连接 → 启动显式事务。失败时会按反向顺序清理。

int ObMViewTransaction::start(
    ObSQLSessionInfo *session_info,
    ObISQLClient *sql_client,
    const uint64_t database_id,
    const ObString &database_name,
    const sql::ObLocalSessionVar *mv_solidified_session_var)
{
  ...
  session_param_saved_.save(session_info, mv_solidified_session_var);
  session_info->set_database_id(database_id);
  session_info->set_default_database(database_name);
  connect(session_info, sql_client);
  start_transaction(tenant_id);
  session_info_ = session_info;
  in_trans_ = true;
  ...
}

int ObMViewTransaction::end(const bool commit)
{
  if (in_trans_) {
    end_transaction(commit);  // commit or rollback
    in_trans_ = false;
  }
  close();                         // 归还内部连接
  session_param_saved_.restore();  // 恢复用户会话
}

关键流程:兼容模式临时切换

Oracle 租户下,内部字典 SQL 往往按 MySQL 语法写。ObMViewTransactionInnerMySQLGuard 用 RAII 把兼容模式切到 MySQL,出作用域再切回来,避免外层用户会话被污染。

ObMViewTransactionInnerMySQLGuard::ObMViewTransactionInnerMySQLGuard(
    ObMViewTransaction &trans)
{
  old_compact_mode_ = trans_.get_compatibility_mode();
  trans_.save_session_for_inner();
  trans_.set_compact_mode(ObCompatibilityMode::MYSQL_MODE);
}

ObMViewTransactionInnerMySQLGuard::~ObMViewTransactionInnerMySQLGuard()
{
  trans_.restore_session_for_inner();
  trans_.set_compact_mode(old_compact_mode_);
}

小结

ob_mview_transaction.cpp 本身不实现 MV 刷新算法,但它为刷新算法提供了一条安全的执行通道:通过栈式保存/恢复会话参数、语句级会话状态以及 Oracle/MySQL 兼容模式,确保内部 SQL 在独立事务中执行,同时不会把任何副作用泄漏给用户的当前会话。理解这套封装,有助于看清 OceanBase 如何在多租户、多兼容模式下安全地执行后台 DDL/DML 任务。

发表回复

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