问题现象
执行统计信息任务的 job,从周一至周日依次有 MONDAY_WINDOW,TUESDAT_WINDOW,WEDNESDAY_WINDOW... 等 7 个 job,执行日期分别对应一周内的每一天,执行周期为一周,保证了每天都会有 job 执行。在升级 OceanBase 数据库 V4.2.1 BP3(oceanbase-4.2.1.3-103000052023122809)版本后,会出现所有的 job 在执行完一次后,下次的执行时间(next_run_date)全部变为了周四早上 8 点,如图。

以图中 MONDAY_WINDOW 为例,原本在 2024-01-08 22:00:00 执行完后,下次执行时间应是 2024-01-15 22:00:00,但下次执行时间计算成了 2024-01-11 08:00:00,即本周四。其他统计信息 job 也会出现同样的现象。
关键诊断信息
问题特征很明显:统计信息 job 在执行完一次后,下次的执行执行时间 (next_run_date)会变为周四早上 8 点:
如果是
MONDAY_WINDOW~WEDNESDAY_WINDOW,下次执行时间会变成本周四。如果是
THURSTDAY_WINDOW~SUNDAY_WINDOW,下次执行时间会变为下周四。
可以观察升级 OceanBase 数据库 V4.2.1 BP3(oceanbase-4.2.1.3-103000052023122809)之后,执行过一次的统计信息 job,其 next_run_date 字段是否当前描述对应。
问题原因
由 dbms_scheduler 本身的两个问题共同导致。
其一是老缺陷:调度器从内部表中把 job 信息读入本地内存时,漏掉了 start_date 字段的读取,导致本地内存中的 start_date 始终为 0 。

其二是 OceanBase 数据库 V4.2.1 BP3(oceanbase-4.2.1.3-103000052023122809)版本对 next date 计算逻辑的改动: 在 OceanBase 数据库 V4.2.1 BP3(oceanbase-4.2.1.3-103000052023122809)版本之前,next_date 是根据 job 本次实际执行时间 this_date + 执行间隔 interval 来计算的,这种计算方法本身并不准确,这是因为调度流程延迟等因素,导致 job 每次实际执行时间会稍晚于计划执行时间,而这种偏差会随着 job 多次运行而逐渐累积,导致偏差越来越大。
从 OceanBase 数据库 V4.2.1 BP3(oceanbase-4.2.1.3-103000052023122809)版本开始,为了修复这个偏差,将 next_date 的计算逻辑改为了根据 job 首次开始时间 start_date + 执行间隔 interval 的整数倍来计算。
由于老缺陷导致 start_date 为 0,next_date 被计算成了 0 + interval 整数倍,对于 interval 为一周的统计信息 job,0 时间戳对应的日期 1970-01-01 正好为周四,就会导致每次计算的 next_date 都落到周四。
问题的风险及影响
此问题会导致以下风险
统计信息 job 全部集中在周四进行,如果租户表很多的话,相当于周四会同时收集很多次,会占用系统资源。
其他六天没有了统计信息收集,会增加统计信息过期的概率。如果中途发生了数据变更,统计信息过期了,跑批 SQL 就会异常,另外比如出现慢 SQL 打满 CPU 的现象,就是统计信息过期导致。
影响租户
影响 OceanBase 数据库中的 SYS 租户和 Oracle 租户以及 MySQL 租户。
影响的版本
OceanBase 数据库 V4.2.1 BP3(oceanbase-4.2.1.3-103000052023122809)及之后版本。
解决方法及规避方式
解决方法:
升级至问题已修复版本(修复方法是增加从内部表中对
job start_date字段的读取)。目前已修复的版本包括 OceanBase 数据库企业版 V4.2.1 BP3 Hotfix2(oceanbase-4.2.1.3-103020012024011011)、V4.2.1 BP3 Hotfix3(oceanbase-4.2.1.3-103030022024011313)、V4.2.1 BP3 Hotfix4()、V4.2.1 BP3 Hotfix5(oceanbase-4.2.1.3-103040012024011620)、V4.2.1 BP4(oceanbase-4.2.1.4-104000062024022914)。规避方式:
手动执行统计信息收集任务。
禁用所有统计信息收集 job。
dbms_scheduler.disable(job_name => 'MONDAY_WINDOW'); dbms_scheduler.disable(job_name => 'TUESDAY_WINDOW'); dbms_scheduler.disable(job_name => 'WEDNESDAY_WINDOW'); dbms_scheduler.disable(job_name => 'THURSDAY_WINDOW');` dbms_scheduler.disable(job_name => 'FRIDAY_WINDOW'); dbms_scheduler.disable(job_name => 'SATURDAY_WINDOW'); dbms_scheduler.disable(job_name => 'SUNDAY_WINDOW');按照每个job对应的统计信息任务,每天手动执行一次

周一
call dbms_stats.GATHER_DATABASE_STATS_JOB_PROC(14400000000);周二call dbms_stats.GATHER_DATABASE_STATS_JOB_PROC(14400000000);周三call dbms_stats.GATHER_DATABASE_STATS_JOB_PROC(14400000000);周四call dbms_stats.GATHER_DATABASE_STATS_JOB_PROC(14400000000);周五call dbms_stats.GATHER_DATABASE_STATS_JOB_PROC(14400000000);周六call dbms_stats.GATHER_DATABASE_STATS_JOB_PROC(72000000000);周日call dbms_stats.GATHER_DATABASE_STATS_JOB_PROC(72000000000);