在 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 任务。