物化视图支持嵌套构建——一个 MV 可以基于另一个 MV 建立,这样就形成了一张”依赖图”。当依赖链上的基表或其他 MV 发生 DDL 变更后,这张依赖图可能过期,如果不及时重建,刷新调度就会拿到错误的依赖关系。本文解读的 ob_mview_update_deps_task.cpp(全文 136 行)就是负责这件事的后台定时任务:ObMViewUpdateDepsTask。它跑在 RootService(RS)侧,周期性探测当前租户是否存在嵌套 MV 依赖,一旦发现就触发整张依赖图的全量重建。代码虽短,却是物化视图元数据一致性的重要守护者。
架构位置与设计动机
该文件位于 src/rootserver/mview 目录,与之前解读过的 MLog 维护任务、MV 定时任务基类、快照清洁任务同属一个家族。ObMViewUpdateDepsTask 继承自 ObTimerTask 定时器框架,由 ObMViewMaintenanceService(MV 维护服务)创建并驱动。它的职责非常单一:只负责”依赖图重建”这一件事。
为什么要单独一个任务?因为依赖图(存储在 __all_mview_dep 系统表中)是刷新调度的输入。如果嵌套 MV 的上游发生了变更而依赖图没更新,下游 MV 的刷新可能漏掉或重复某些依赖。与其在每次 DDL 后同步重建(增加 DDL 时延),不如用一个低频后台任务异步巡检——这是典型的”最终一致”设计取舍。
关键流程:两道闸门 + 一次重建
整个任务的核心逻辑在 runTimerTask 中,外层套了两道”闸门”来控制实际工作频率:
void ObMViewUpdateDepsTask::runTimerTask()
{
int ret = OB_SUCCESS;
bool need_sched = false;
int64_t curr_ts = ObTimeUtil::current_time();
if (curr_ts - last_sched_ts_ > 20 * 1000 * 1000) { // 闸门①:20秒软节流
if (OB_FAIL(need_schedule(need_sched))) {
LOG_WARN("fail to check need schedule", K(ret), K(need_sched));
}
}
if (OB_FAIL(ret) || !need_sched) { // 失败或不需要:静默跳过
} else if (OB_FAIL(MTL(ObMViewMaintenanceService*)->get_all_mview_deps())) {
LOG_WARN("update_mview_mds_op failed", KR(ret)); // 闸门②通过:真正重建依赖图
} else {
last_sched_ts_ = curr_ts;
}
}
定时器周期本身可能比 20 秒短,所以第一道闸门用 last_sched_ts_ 做软节流:距上次真正调度不足 20 秒直接跳过。第二道闸门是 need_schedule 的布尔探测——没有嵌套 MV 时完全不干活,连重建入口都不碰。两道都通过才调用维护服务的 get_all_mview_deps() 重建整张依赖图,并刷新节流时间戳。这里 OB_FAIL(ret) || !need_sched 配空分支的写法在 OceanBase 中很常见:条件不满足时静默通过,只有”成功且需要”才走主逻辑。
need_schedule:一条自连接 SQL 探测嵌套依赖
判断是否需要重建的依据非常巧妙——一条 SELECT EXISTS 自连接查询:
sql.assign_fmt("SELECT EXISTS ("
"SELECT 1 FROM `%s`.`%s` t1, `%s`.`%s` t2"
" WHERE t1.mview_id = t2.p_obj AND t1.tenant_id = %lu) as exist_nested",
OB_SYS_DATABASE_NAME, OB_ALL_MVIEW_DEP_TNAME,
OB_SYS_DATABASE_NAME, OB_ALL_MVIEW_DEP_TNAME, ...);
__all_mview_dep 表存储 MV 的依赖关系(mview_id → 被依赖对象 p_obj)。把它自连接,条件是 t1.mview_id = t2.p_obj——含义是”某个 MV 自身又被另一个 MV 依赖”。存在这种记录就说明系统中有嵌套 MV,依赖图才存在过期的可能;如果全是”MV → 基表”的扁平依赖,DDL 变更时上游会同步处理,无需后台巡检。SELECT EXISTS 让内核一次只返回一个布尔值,避免拉取任何明细数据。
细节上还有两点值得注意:其一,tenant_id 经过 get_extract_tenant_id 转换,保证任务跑在 sys 租户上下文时也能按普通租户正确过滤;其二,结果提取用 EXTRACT_BOOL_FIELD_MYSQL 宏统一处理,出错路径干净。整个函数通过 GCTX.sql_proxy_(RS 的 SQL 代理)读内部表,这也是 rootserver 模块访问元数据的标准姿势。
生命周期:init / start / stop / destroy
任务的生命周期是 OceanBase 后台任务的标准模板。init 里用 IS_INIT 宏防重复初始化(返回 OB_INIT_TWICE),只记录当前租户 ID(MTL_ID()),不做重活;start 里先置 is_stop_ = false,再用 in_sched_ 标志防止定时器句柄被重复注册——schedule_task(interval, true) 的第二个参数 true 表示按 MVIEW_UPDATE_DEPS_INTERVAL 周期重复调度。
stop 和 destroy 的区别在于:stop 只 cancel_task() 取消调度;destroy 额外调用 wait_task() 等待正在执行的 runTimerTask 返回,保证析构时没有并发执行体在跑,避免悬空引用。顺序上必须先 cancel 再 wait——先停止新调度,再等旧执行体退出,不可颠倒。
小结
ob_mview_update_deps_task 是一个不足 150 行的”小而美”后台任务:一条 EXISTS 自连接 SQL 做廉价探测,20 秒节流 + 布尔闸门双重防抖,确认存在嵌套依赖才触发依赖图全量重建。它体现了 OceanBase 后台任务设计的三个惯用手法——软节流(last_sched_ts_ 时间戳)、防重复调度(in_sched_ 标志)、cancel 先于 wait 的安全销毁顺序。配合此前解读的 MLog 维护任务、MDS 操作记录同步任务,物化视图在 RS 侧的元数据一致性保障体系已经比较完整了。