OceanBase源码解读:物化视图定时任务 ob_mview_timer_task

物化视图的”major 合并刷新”是 OceanBase 4.3.4 引入的新刷新模式:不再逐条回放增量日志,而是直接把物化视图随租户的合并(major compaction)一起物化到基线数据里。这套机制要运转起来,必须有一个站在 RootService 侧、周期性观察”租户合并进度”并决定”要不要把新合并版本推给物化视图”的调度器——这就是 ob_mview_timer_task.cpp 里 ObMViewTimerTask 的职责。整个文件不到 200 行,却把”定时器外壳 + 调度判定 + 一致性自检”三件事讲得非常清楚,是理解 MV 与合并联动机制的最佳入口。

一、文件定位:MV 定时任务体系的基座

在 src/rootserver/mview/ 目录下,围绕物化视图的后台任务有一整个家族:MLog 维护任务、清理快照任务、推送快照任务、推送合并版本任务等。ObMViewTimerTask 是这些任务共同的调度外壳——它继承定时器任务基类,把”挂载到租户共享定时器”的三个标准动作(调度、取消、等待)封装成对称的三个方法:

int ObMViewTimerTask::schedule_task(const int64_t delay, bool repeate, bool immediate)
{
  int ret = OB_SUCCESS;
  omt::ObSharedTimer *timer = MTL(omt::ObSharedTimer *);
  if (OB_ISNULL(timer)) {
    ret = OB_INNER_STAT_ERROR;
    LOG_WARN("shared timer is NULL", KR(ret));
  } else if (OB_FAIL(TG_SCHEDULE(timer->get_tg_id(), *this, delay, repeate, immediate))) {
    LOG_WARN("fail to schedule mview timer task", KR(ret), KP(this), K(delay), K(repeate),
             K(immediate));
  }
  return ret;
}

注意这里的 MTL() 宏:它按当前租户上下文取到租户级单例 omt::ObSharedTimer,然后用 TG_SCHEDULE 把任务注册进定时器线程组。delay 是首次触发延迟,repeate 控制是否周期执行,immediate 控制是否立刻跑一次。与之对应的 cancel_task()(取消)和 wait_task()(阻塞等待执行完)构成标准的生命周期三件套——典型用法是停租户前先 cancel 再 wait,保证没有 MV 定时任务还在跑。

二、要不要调度:三层过滤的判断闸门

need_schedule_major_refresh_mv_task() 是推送合并版本前的前置闸门,用三层过滤避免无谓开销:

if (OB_FAIL(GET_MIN_DATA_VERSION(tenant_id, data_version))) {
  LOG_WARN("fail to get data version", KR(ret), K(tenant_id));
} else if (OB_UNLIKELY(data_version < DATA_VERSION_4_3_4_0)) {
} else if (tenant_id == OB_SYS_TENANT_ID) {
  // skip sys tenant
} else if (OB_UNLIKELY(OB_ISNULL(sql_proxy))) {
  ret = OB_ERR_UNEXPECTED;
} else if (OB_FAIL(snapshot_proxy.check_snapshot_exist(*sql_proxy, tenant_id,
                                                       share::SNAPSHOT_FOR_MAJOR_REFRESH_MV,
                                                       contains_major_refresh_mview))) {
  LOG_WARN("fail to check if tenant contains major refresh snapshot", KR(ret), K(tenant_id));
} else if (contains_major_refresh_mview) {
  need_schedule = true;
}

三层依次是:数据版本过滤(major 刷新模式 4.3.4 才引入,老版本直接跳过,兼容滚动升级场景);租户过滤(sys 租户没有用户物化视图);快照存在性检查——查内部表看该租户是否存在 SNAPSHOT_FOR_MAJOR_REFRESH_MV 类型的快照。最后这一层有个设计上的巧思:它不是去查”有哪些物化视图”,而是查”major 刷新快照是否存在”。这样即使快照已建、刷新还没完成的中间态也能被覆盖到,判断依据更贴近真实的执行进度。

三、要不要推送:两个 SCN 的对比决策

need_push_major_mv_merge_scn() 是整个文件的核心,回答”租户完成了一轮新合并,是否需要把该版本推送给物化视图”。它取两个 SCN 做对比:

  • lastest_merge_scn:从 ObTenantTabletScheduler(合并调度器)读租户当前最新合并版本;
  • major_mv_merge_scn:从全局统计表(ObGlobalStatProxy)读上一次已推送记录的 major MV 合并版本。

取到之后是四分支决策:

if (OB_FAIL(ret)) {
} else if (!need_schedule) {
} else if (!lastest_merge_scn.is_valid() || !major_mv_merge_scn.is_valid()) {
  ret = OB_ERR_UNEXPECTED;    // 内部状态损坏
} else if (lastest_merge_scn < major_mv_merge_scn) {
  ret = OB_ERR_UNEXPECTED;    // 已推送版本不可能超前于实际合并
} else if (lastest_merge_scn == major_mv_merge_scn) {
  // 该版本已处理过,无需推送
} else {
  need_push = true;           // 新合并完成而 major MV 还没跟上 → 推送
}

注意第二分支:lastest_merge_scn < major_mv_merge_scn 是不应当发生的不变量——已推送的 MV 版本不可能超前于租户实际完成的合并版本。一旦出现就报错,说明内部表状态出了严重问题。这种”把不变量检查显式写进决策链”的写法,既兜住了异常状态,也让代码逻辑自文档化。

四、推送后的自检:MIN(LAST_REFRESH_SCN) 的一致性

check_mview_last_refresh_scn() 在推送之后做一致性兜底。它对 __all_mview 内部表执行一条聚合 SQL:

SELECT MIN(LAST_REFRESH_SCN) AS MIN_LAST_REFRESH_SCN
FROM `oceanbase`.`__all_mview`
WHERE (REFRESH_MODE = 2) AND (LAST_REFRESH_TYPE = 1)

即只统计 REFRESH_MODE=MAJOR_COMPACTION 且 LAST_REFRESH_TYPE=FAST(增量刷新)的物化视图,取其最小刷新版本。校验逻辑是:所有 major 模式的物化视图都刷新完成后,这个最小版本应当恰好等于推送下去的 major_mv_merge_scn。三个放行条件分别是:结果为 min/base(版本还没起步,正常)、相等(一致),否则直接 LOG_ERROR——注意这里是 ERROR 级别而非 WARN,意味着触发即视为数据不一致的严重故障,需要人工介入排查,它是一个诊断信号而不是自动修复路径。

五、小结

ob_mview_timer_task.cpp 虽小,信息量却很密:它揭示了 OceanBase 4.3.4 物化视图 major 刷新机制的三层结构——定时器外壳(ObSharedTimer + 任务即对象模式)、调度判定(数据版本 → 租户 → 快照存在性的三级闸门,加上两个 SCN 的对比决策)、一致性自检(内部表最小刷新版本 vs 推送版本的 ERROR 级校验)。核心思想一句话:物化视图的 major 刷新本质是”搭租户合并的便车”,而这个定时器就是站在 RS 视角、保证 MV 版本始终紧跟租户合并进度的哨兵。把这两个 SCN 的推进逻辑弄明白,再看 ob_mview_push_refresh_scn_task、ob_mview_maintenance_task 等兄弟文件时会顺畅很多。

发表回复

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