Oracle Auto Stats 明明正常,为什么 519 张表一年没更新?

举报
Lucifer三思而后行 发表于 2026/09/23 09:08:22 2026/09/23
【摘要】 前言前面我们已经写了 3 篇文章来讨论过这个问题:凌晨 1 点 Oracle 宕机:1TB 归档盘怎么突然就满了?一条 SELECT,为什么能让 Oracle 产生 384GB Redo?从 450GB 归档盘打满,到一条 SELECT 的真相!第 3 篇最后,那条反复 Full Table Scan 的 SQL 已经处理完,单次 Physical Reads 从两千多万降到了几千,red...

前言

前面我们已经写了 3 篇文章来讨论过这个问题:

  1. 凌晨 1 点 Oracle 宕机:1TB 归档盘怎么突然就满了?
  2. 一条 SELECT,为什么能让 Oracle 产生 384GB Redo?
  3. 从 450GB 归档盘打满,到一条 SELECT 的真相!

第 3 篇最后,那条反复 Full Table Scan 的 SQL 已经处理完,单次 Physical Reads 从两千多万降到了几千,redo size for lost write detection 跟着回落,归档也从事故期间的 30 ~ 50GB/h 回到了 2 ~ 5GB/h。

从业务恢复的角度看,这次事故已经结束。

但当时还留下了一个没有继续深挖的问题:TASK_PARAM_DETAIL 的统计信息为什么会被锁?而且它到底是一张表的问题,还是整套库都存在类似情况?

当时没有足够的信息判断这些 Stats Lock 是谁加的、为什么加的,所以我先不猜原因,而是继续确认范围:到底是这一张表碰巧被锁了,还是这套库的统计信息本来就有问题?

本文记录完整的分析以及处理过程,希望对大家有所帮助。

发现更多统计信息严重失真的大表

首先,我是从 APP 里另外几条高 Physical Reads SQL 开始看的,其中两张核心表是 BIZ_BATCHBIZ_DETAIL_PARAM

先看统计信息:

ALTER SESSION SET CONTAINER=APP;

SELECT table_name,
       stattype_locked,
       num_rows,
       blocks,
       avg_row_len,
       TO_CHAR(last_analyzed,'YYYY-MM-DD HH24:MI') last_analyzed
FROM dba_tab_statistics
WHERE owner='APP'
  AND table_name IN ('BIZ_DETAIL_PARAM','BIZ_BATCH')
  AND object_type='TABLE'
ORDER BY table_name;

TABLE_NAME       STATTYPE_LOCKED   NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED
---------------- ---------------- ---------- ---------- ----------- ----------------
BIZ_BATCH         ALL                1507349      66834         308 2025-03-20 13:01
BIZ_DETAIL_PARAM   ALL               33726965     817915         167 2025-03-20 13:01

两张表的最后分析时间都停在 2025-03-20STATTYPE_LOCKED 都是 ALL,而且都是 APP 里持续有大量 DML 的核心业务表。

再看实际 Segment:

ALTER SESSION SET CONTAINER=CDB$ROOT;

SELECT p.name pdb_name,
       s.owner,
       s.segment_name,
       s.segment_type,
       ROUND(SUM(s.bytes)/1024/1024/1024,2) size_gb
FROM cdb_segments s
JOIN v$pdbs p
  ON p.con_id=s.con_id
WHERE s.owner='APP'
  AND s.segment_name IN ('BIZ_DETAIL_PARAM','BIZ_BATCH')
GROUP BY p.name,s.owner,s.segment_name,s.segment_type
ORDER BY size_gb DESC;

PDB_NAME  OWNER  SEGMENT_NAME     SEGMENT_TYPE   SIZE_GB
--------- ------ ---------------- -------------- --------
APP       APP    BIZ_DETAIL_PARAM   TABLE            421.63
APP       APP    BIZ_BATCH         TABLE             24.02

BIZ_DETAIL_PARAM 已经 421GB,统计信息却还认为它只有 3372 万行、81 万多个 block。

再看 dba_tab_modifications

ALTER SESSION SET CONTAINER=APP;

SELECT table_owner,
       table_name,
       inserts,
       updates,
       deletes,
       timestamp
FROM dba_tab_modifications
WHERE table_owner='APP'
  AND table_name IN ('BIZ_DETAIL_PARAM','BIZ_BATCH');

TABLE_OWNER TABLE_NAME        INSERTS       UPDATES DELETES TIMESTAMP
----------- ---------------- ------------ -------- ------- ---------
APP         BIZ_BATCH             70372416 71328573     800 22-SEP-26
APP         BIZ_DETAIL_PARAM     2388755943  6378518   11018 22-SEP-26

BIZ_DETAIL_PARAM 自上次统计信息维护以后,光 INSERT 监控值就已经到了 23 亿级。

这明显是有问题的,但是在真正处理之前,最好先把旧 Stats 备份下来:

BEGIN
    DBMS_STATS.CREATE_STAT_TABLE(
        ownname => 'APP',
        stattab => 'BIZ_STATS_BAK'
    );

    DBMS_STATS.EXPORT_TABLE_STATS(
        ownname => 'APP',
        tabname => 'BIZ_DETAIL_PARAM',
        stattab => 'BIZ_STATS_BAK',
        statid  => 'BEFORE_20260922'
    );

    DBMS_STATS.EXPORT_TABLE_STATS(
        ownname => 'APP',
        tabname => 'BIZ_BATCH',
        stattab => 'BIZ_STATS_BAK',
        statid  => 'BEFORE_20260922'
    );
END;
/

然后单独解锁并重新收集,再处理 BIZ_DETAIL_PARAM

BEGIN
    DBMS_STATS.UNLOCK_TABLE_STATS(
        ownname => 'APP',
        tabname => 'BIZ_DETAIL_PARAM'
    );

    DBMS_STATS.GATHER_TABLE_STATS(
        ownname          => 'APP',
        tabname          => 'BIZ_DETAIL_PARAM',
        estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
        method_opt       => 'FOR ALL COLUMNS SIZE AUTO',
        degree           => 8,
        cascade          => TRUE,
        no_invalidate    => FALSE
    );
END;
/

PL/SQL procedure successfully completed.

SELECT table_name,
       num_rows,
       blocks,
       avg_row_len,
       TO_CHAR(last_analyzed,'YYYY-MM-DD HH24:MI') last_analyzed,
       stattype_locked
FROM dba_tab_statistics
WHERE owner='APP'
  AND table_name='BIZ_DETAIL_PARAM'
  AND object_type='TABLE';

TABLE_NAME        NUM_ROWS       BLOCKS AVG_ROW_LEN LAST_ANALYZED
---------------- --------------- ----------- ----------- ----------------
BIZ_DETAIL_PARAM      2348319750    55209166         165 2026-09-22 13:22

原来 3372 万行,现在 23.48 亿行,差了接近 70 倍,接着处理 BIZ_BATCH

BEGIN
    DBMS_STATS.UNLOCK_TABLE_STATS(
        ownname => 'APP',
        tabname => 'BIZ_BATCH'
    );

    DBMS_STATS.GATHER_TABLE_STATS(
        ownname          => 'APP',
        tabname          => 'BIZ_BATCH',
        estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
        method_opt       => 'FOR ALL COLUMNS SIZE AUTO',
        degree           => 8,
        cascade          => TRUE,
        no_invalidate    => FALSE
    );
END;
/

PL/SQL procedure successfully completed.

SELECT table_name,
       num_rows,
       blocks,
       avg_row_len,
       sample_size,
       TO_CHAR(last_analyzed,'YYYY-MM-DD HH24:MI:SS') last_analyzed
FROM dba_tab_statistics
WHERE owner='APP'
  AND table_name='BIZ_BATCH'
  AND object_type='TABLE';

TABLE_NAME  NUM_ROWS   BLOCKS   AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
----------- ---------- -------- ----------- ----------- -------------------
BIZ_BATCH     70387355  3144724         320    70387355 2026-09-22 16:17:44

得,又是一张,BIZ_BATCH 从 150 万行变成 7039 万行,Blocks 从 6.6 万变成 314
万。新的 Blocks 和前面从 Segment 看到的实际规模已经基本对上。

分析到这里,我感觉这个库的所有标的统计信息可能都被锁了,因为最后的分析时间都完全一样,而且全部 STATTYPE_LOCKED=ALL,肯定不再继续一张张修了。

全库 519 张表的统计信息都被锁了

这套环境是 19c CDB/PDB 架构,为了方便查询(不用一直切换 PDB),所以 SQL 查询我们都尽量用 CDB_* 视图。

查看所有表的统计信息:

ALTER SESSION SET CONTAINER=CDB$ROOT;

SELECT p.name pdb_name,
       t.owner,
       t.table_name,
       t.stattype_locked,
       t.num_rows,
       t.blocks,
       TO_CHAR(t.last_analyzed,'YYYY-MM-DD HH24:MI') last_analyzed
FROM cdb_tab_statistics t
JOIN v$pdbs p
  ON p.con_id=t.con_id
WHERE t.object_type='TABLE'
  AND t.stattype_locked IS NOT NULL
  AND t.owner NOT IN (
      'SYS','SYSTEM','XDB','WMSYS','CTXSYS',
      'MDSYS','ORDSYS','GSMADMIN_INTERNAL'
  )
ORDER BY p.name,t.owner,t.last_analyzed NULLS FIRST,t.table_name;

519 rows selected.

SELECT p.name pdb_name,
       t.owner,
       COUNT(*) locked_tables,
       MIN(t.last_analyzed) oldest_stats,
       MAX(t.last_analyzed) newest_stats
FROM cdb_tab_statistics t
JOIN v$pdbs p
  ON p.con_id=t.con_id
WHERE t.object_type='TABLE'
  AND t.stattype_locked IS NOT NULL
  AND t.owner NOT IN (
      'SYS','SYSTEM','XDB','WMSYS','CTXSYS',
      'MDSYS','ORDSYS','GSMADMIN_INTERNAL'
  )
GROUP BY p.name,t.owner
ORDER BY locked_tables DESC;

PDB_NAME  OWNER    LOCKED_TABLES OLDEST_STATS NEWEST_STATS
--------- -------- ------------- ------------ ------------
APP       APP                236 19-DEC-24    11-NOV-25
APP2       APP2                133 26-JUL-24    11-NOV-25
APP3    APP3             110 30-DEC-24    26-NOV-25
APP4       APP4                 40 22-AUG-25    05-SEP-25

四个业务 PDB,一共 519 张表,最早的统计信息已经停在 2024 年。看到这里,感觉有点不妙:为什么这套数据库会有 519 张业务表长期处于 Stats Locked?

如果这是人为设计的统计信息管理策略,那很可能还存在另外一套手工统计信息收集机制;如果没有,那么这些表就等于长期脱离了 Oracle 自动统计信息维护。

所以这里我没有先直接批量 UNLOCK,而是打算继续把统计信息维护机制捋一捋。

Auto Stats 到底有没有开?

先看一下 Oracle 自带的自动统计信息任务:

SELECT client_name,
       status,
       attributes
FROM dba_autotask_client
WHERE client_name='auto optimizer stats collection';

CLIENT_NAME                         STATUS  ATTRIBUTES
----------------------------------- ------- --------------------------------------------
auto optimizer stats collection    ENABLED ON BY DEFAULT, VOLATILE, SAFE TO KILL

是开启的,再看最近有没有真正执行:

SELECT client_name,
       job_status,
       job_start_time,
       job_duration,
       job_error
FROM dba_autotask_job_history
WHERE client_name='auto optimizer stats collection'
  AND job_start_time > SYSDATE-7
ORDER BY job_start_time DESC;

CLIENT_NAME                       JOB_STATUS JOB_START_TIME                  JOB_DURATION JOB_ERROR
--------------------------------- ---------- ------------------------------- ------------ ---------
auto optimizer stats collection  SUCCEEDED  21-SEP-26 10.00.05.696882 PM  +00 00:01:06         0
auto optimizer stats collection  SUCCEEDED  20-SEP-26 10.06.55.095742 PM  +00 00:00:10         0
auto optimizer stats collection  SUCCEEDED  20-SEP-26 06.05.39.586139 PM  +00 00:00:08         0
...

任务是开着的,也一直在正常执行,维护窗口本身也正常。

这就出现了一个很容易误判的场景:Oracle Auto Stats 每天都在正常运行,Job History 也是 SUCCEEDED,但业务表的统计信息却一直没有更新。

有没有另外一套人工统计信息任务?

生产库里有一种做法:为了控制执行计划变化,先把 Stats Lock 住,再由自己的 Scheduler Job 在固定时间解锁、Gather、重新 Lock。如果业务原本就是这种设计,直接把 519 张表全部放开就可能破坏原来的维护策略。

所以我没有直接蛮干,继续往下查 Scheduler:

SELECT p.name pdb_name,
       j.owner,
       j.job_name,
       j.enabled,
       j.state,
       j.job_type,
       j.repeat_interval,
       j.last_start_date,
       j.next_run_date
FROM cdb_scheduler_jobs j
JOIN v$pdbs p
  ON p.con_id=j.con_id
WHERE UPPER(j.job_name) LIKE '%STAT%'
   OR UPPER(j.job_action) LIKE '%DBMS_STATS%'
   OR UPPER(j.program_name) LIKE '%STAT%'
ORDER BY p.name,j.owner,j.job_name;

查询结果的主要是 Oracle 自己的 SYS.BSLN_MAINTAIN_STATS_JOB,没有看到 APP、APP2、APP3、APP4 下维护的 DBMS_STATS 的业务 Job。

以防万一,再查一遍老版本的 DBMS_JOB

SELECT p.name pdb_name,
       j.schema_user,
       j.job,
       j.broken,
       j.last_date,
       j.next_date,
       j.interval,
       j.what
FROM cdb_jobs j
JOIN v$pdbs p
  ON p.con_id=j.con_id
WHERE UPPER(j.what) LIKE '%DBMS_STATS%'
   OR UPPER(j.what) LIKE '%GATHER%'
   OR UPPER(j.what) LIKE '%STAT%'
ORDER BY p.name,j.schema_user,j.job;

no rows selected

也没有发现相关的任务,又继续扫了数据库源码里对 DBMS_STATSLOCK_TABLE_STATSGATHER_TABLE_STATSGATHER_SCHEMA_STATS 的存储过程调用,看到的主要也是 Oracle 的原生代码,没有找到一套业务侧定时接管统计信息的机制。

到这里,证据基本对上了:Auto Stats 正常,维护窗口正常,最近任务持续成功,但业务表大量 STATTYPE_LOCKED=ALL,同时没有发现另外一套人工 Stats Job 在负责这些对象的统计信息收集。

所以,现阶段真正需要处理的不是"重新建一个统计信息 Job",而是把这些历史 Lock 打开,让 Oracle 原来的自动维护重新接管。

旧统计已经偏到了什么程度?

在真正开始批量解锁之前,我又把 APP 里大表和修改量单独过了一遍,几张典型表的变化是:

对象                       旧 NUM_ROWS       新 NUM_ROWS
------------------------- --------------- ----------------
BIZ_DETAIL_PARAM               33,726,965     2,348,319,750
BIZ_BATCH                      1,507,349        70,387,355
BIZ_API_LOG                     250,381       132,275,994
BIZ_ASSOCIATION          1,510,326        70,580,499
BIZ_MATERIAL_CONSUM            1,582,761        60,686,279
BIZ_RUNTIME                182,703         5,853,098
BIZ_API_LOG_EXT                     278         2,649,135
BIZ_NG_RECORD               114,573         4,578,031
BIZ_EQUIPMENT_ALARM              280,610         9,028,330
BIZ_PAIR_RECORD                  110,632         3,815,143

其中 BIZ_API_LOG 最夸张,这张表实际已经接近 194GB,旧 Stats 里却只有 25 万行,重新收集后:

SELECT table_name,
       num_rows,
       blocks,
       sample_size,
       stale_stats,
       TO_CHAR(last_analyzed,'YYYY-MM-DD HH24:MI:SS') last_analyzed
FROM dba_tab_statistics
WHERE owner='APP'
  AND table_name='BIZ_API_LOG';

TABLE_NAME    NUM_ROWS   BLOCKS    SAMPLE_SIZE STALE_STATS LAST_ANALYZED
------------- ---------- --------- ----------- ----------- -------------------
BIZ_API_LOG   132275994  25397227   132275994  NO          2026-09-22 20:09:41

从 25 万变成 1.32 亿,差了 500 多倍,BIZ_API_LOG_EXT 表更极端,旧统计只有 278 行,重新收集以后是 264 万。

这里需要注意,DBA_TAB_MODIFICATIONS 里的 INSERT/UPDATE/DELETE 是修改监控,不应该简单拿 CHANGES / NUM_ROWS 当成"当前真实增长倍数"。同一批数据可能被反复 UPDATE,真正能判断当前规模的,还是要以重新收集后的 NUM_ROWSBLOCKS
和实际 Segment 为准。

但这些结果已经足够说明一件事:CBO 看到的库,和真实的库明显已经不是同一个量级。

519 张表,我没有直接一把全收

问题已经确认以后,下一步就是恢复统计信息维护,但这里我还是没有直接把 519 张表全部 UNLOCK,然后来一把全库GATHER_SCHEMA_STATS

原因很简单:APP 里有 421GB 的 BIZ_DETAIL_PARAM、194GB 的 BIZ_API_LOG,还有一批十几 GB 的业务表,如果一次性把所有历史积压全部放开,再让维护窗口自己处理,第一次执行到底会产生多大 I/O、跑多久,都不好判断。

所以我还是打算分批处理:先处理 APP,因为当前故障和重 SQL 都集中在这里,确认 APP 的 236 张业务表后统一解除 Lock,但先不立刻全 Schema Gather:

ALTER SESSION SET CONTAINER=APP;

BEGIN
    FOR r IN (
        SELECT table_name
        FROM dba_tab_statistics
        WHERE owner='APP'
          AND object_type='TABLE'
          AND stattype_locked IS NOT NULL
    )
    LOOP
        BEGIN
            DBMS_STATS.UNLOCK_TABLE_STATS(
                ownname => 'APP',
                tabname => r.table_name
            );
        EXCEPTION
            WHEN OTHERS THEN
                DBMS_OUTPUT.PUT_LINE(r.table_name || ' : ' || SQLERRM);
        END;
    END LOOP;
END;
/

PL/SQL procedure successfully completed.

SELECT COUNT(*) locked_tables
FROM dba_tab_statistics
WHERE owner='APP'
  AND object_type='TABLE'
  AND stattype_locked IS NOT NULL;

LOCKED_TABLES
-------------
            0

接着看解锁后真正等待维护的对象,当时 APP 一共有 155 张 STALE_STATS=YES

APP2、APP3、APP4 的对象规模明显小很多,所以确认完 Locked/Stale 和大表规模以后,也依次解除业务 Schema 的 Lock,APP3 最后保留了一张历史备份表 HISTORY_CONFIG_BAK 的 Lock,没有为了让数字归零强行处理。

先用三个小 PDB 验证 GATHER AUTO

这里我没有直接对所有对象强制重新收集,而是选择按照 PDB:

DBMS_STATS.GATHER_SCHEMA_STATS(
    ownname          => '<SCHEMA>',
    options          => 'GATHER AUTO',
    estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
    method_opt       => 'FOR ALL COLUMNS SIZE AUTO',
    degree           => 4,
    cascade          => DBMS_STATS.AUTO_CASCADE,
    no_invalidate    => DBMS_STATS.AUTO_INVALIDATE
);

先从 APP3 开始:

ALTER SESSION SET CONTAINER=APP3;

BEGIN
    DBMS_STATS.GATHER_SCHEMA_STATS(
        ownname          => 'APP3',
        options          => 'GATHER AUTO',
        estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
        method_opt       => 'FOR ALL COLUMNS SIZE AUTO',
        degree           => 4,
        cascade          => DBMS_STATS.AUTO_CASCADE,
        no_invalidate    => DBMS_STATS.AUTO_INVALIDATE
    );
END;
/

PL/SQL procedure successfully completed.
Elapsed: 00:00:02.27

SELECT stale_stats,
       COUNT(*) cnt
FROM dba_tab_statistics
WHERE owner='APP3'
  AND object_type='TABLE'
GROUP BY stale_stats
ORDER BY stale_stats;

STALE_STATS   CNT
----------- -----
NO            112

APP3 原来 27 张 stale 表,GATHER AUTO 只用了 2.27 秒就处理完,并没有把所有对象机械地重新收一遍。

APP2 和 APP4 继续用同样方式:

APP3   2.27 秒   STALE=0
APP2      7.32 秒   STALE=0
APP4      1.69 秒   STALE=0

三个小 PDB 都正常以后,最后再回到 APP。

APP 当时还有 155 张 stale 表,其中最大的就是前面提到的 BIZ_API_LOG,直接使用同样的 GATHER AUTO

ALTER SESSION SET CONTAINER=APP;

SET TIMING ON

BEGIN
    DBMS_STATS.GATHER_SCHEMA_STATS(
        ownname          => 'APP',
        options          => 'GATHER AUTO',
        estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
        method_opt       => 'FOR ALL COLUMNS SIZE AUTO',
        degree           => 4,
        cascade          => DBMS_STATS.AUTO_CASCADE,
        no_invalidate    => DBMS_STATS.AUTO_INVALIDATE
    );
END;
/

PL/SQL procedure successfully completed.
Elapsed: 00:04:18.57

Oracle 会根据现有统计信息状态判断需要处理的对象,我们要解决的是历史积压,不是为了“全部重收一遍”制造新的 I/O。执行过程中,很多旧统计信息被快速修正,比如:

SELECT table_name,
       num_rows,
       blocks,
       sample_size,
       stale_stats,
       TO_CHAR(last_analyzed,'YYYY-MM-DD HH24:MI:SS') last_analyzed
FROM dba_tab_statistics
WHERE owner='APP'
  AND table_name IN (
      'BIZ_ASSOCIATION',
      'BIZ_RUNTIME',
      'BIZ_MATERIAL_CONSUM',
      'BIZ_API_LOG',
      'BIZ_OPER_LOG'
  )
ORDER BY table_name;

TABLE_NAME              NUM_ROWS   BLOCKS    SAMPLE_SIZE STALE LAST_ANALYZED
----------------------- ---------- --------- ----------- ----- -------------------
BIZ_ASSOCIATION     70580499   1639253    70580499 NO    2026-09-22 20:08:25
BIZ_RUNTIME          5853098    392348     5853098 NO    2026-09-22 20:08:56
BIZ_MATERIAL_CONSUM       60686279   2246914    60686279 NO    2026-09-22 20:09:13
BIZ_API_LOG             132275994  25397227   132275994 NO    2026-09-22 20:09:41
BIZ_OPER_LOG              1677169   2011878     1677169 NO    2026-09-22 20:12:14

BIZ_API_LOG 从旧统计的 25 万行修正到 1.32 亿,BIZ_OPER_LOG 从 8 万多行修正到 167 万。

这时候再回头看第 3 篇里那些离谱的基数估算,其实已经不奇怪了。优化器不是"明知道这是一张几十亿行的大表还故意选错",而是它手里的对象统计根本没有跟上真实数据。

当然,统计信息准确不代表所有 SQL 都会自动变快,索引设计、SQL 写法、数据分布、Bind、直方图仍然都会影响执行计划。但如果连表到底有 3000 万行还是 23 亿行都不知道,后面的成本估算本身就已经失去了可靠基础。

最终验收

所有处理完成以后,最后从 CDB$ROOT 做一次统一验收:

ALTER SESSION SET CONTAINER=CDB$ROOT;

SELECT p.name pdb_name,
       x.owner,
       COUNT(*) tables_cnt,
       SUM(CASE WHEN x.stale_stats='YES' THEN 1 ELSE 0 END) stale_tables,
       SUM(CASE WHEN x.stattype_locked IS NOT NULL THEN 1 ELSE 0 END) locked_tables,
       SUM(CASE WHEN x.last_analyzed IS NULL THEN 1 ELSE 0 END) no_stats_tables
FROM (
    SELECT con_id,
           owner,
           table_name,
           MAX(stale_stats) stale_stats,
           MAX(stattype_locked) stattype_locked,
           MAX(last_analyzed) last_analyzed
    FROM cdb_tab_statistics
    WHERE object_type='TABLE'
    GROUP BY con_id,owner,table_name
) x
JOIN v$pdbs p
  ON p.con_id=x.con_id
WHERE (p.name='APP'    AND x.owner='APP')
   OR (p.name='APP2'    AND x.owner='APP2')
   OR (p.name='APP3' AND x.owner='APP3')
   OR (p.name='APP4'    AND x.owner='APP4')
GROUP BY p.name,x.owner
ORDER BY p.name;

PDB_NAME  OWNER   TABLES_CNT STALE_TABLES LOCKED_TABLES NO_STATS_TABLES
--------- ------- ---------- ------------ ------------- ---------------
APP       APP            252            0             0               1
APP2       APP2            139            0             0               1
APP3    APP3         112            0             1               0
APP4       APP4             46            0             0               0

APP 和 APP2 各有一个 NO_STATS,继续查出来都是:

SELECT p.name pdb_name,
       t.owner,
       t.table_name,
       t.stale_stats,
       t.stattype_locked,
       t.num_rows,
       t.blocks,
       TO_CHAR(t.last_analyzed,'YYYY-MM-DD HH24:MI') last_analyzed
FROM cdb_tab_statistics t
JOIN v$pdbs p
  ON p.con_id=t.con_id
WHERE t.object_type='TABLE'
  AND t.last_analyzed IS NULL
  AND (
       (p.name='APP' AND t.owner='APP')
    OR (p.name='APP2' AND t.owner='APP2')
  );

PDB_NAME OWNER TABLE_NAME    STALE_STATS STATTYPE_LOCKED NUM_ROWS BLOCKS LAST_ANALYZED
-------- ----- ------------- ----------- --------------- -------- ------ -------------
APP      APP   SYS_TEMP_FBT
APP2      APP2   SYS_TEMP_FBT

属于特殊对象,没有必要为了让 NO_STATS_TABLES 变成 0 再强行收集。

APP3 唯一保留 Lock 的也是一张历史备份表:

APP3.HISTORY_CONFIG_BAK

到这里,业务统计信息的状态已经很干净了:APP、APP2、APP3、APP4 全部 STALE_STATS=0,正常业务表不再大面积 Locked,Oracle 原生 Auto Stats 也保持 ENABLED,后续达到 stale 条件的对象重新交给维护窗口自动处理。

到这里,这次故障才算真正结束

第 3 篇最后,SQL 已经恢复,Physical Reads 下来了,Lost Write Detection 产生的 Redo 也跟着回落,归档从事故期间的 30 ~ 50GB/h 回到了 2 ~ 5GB/h。

当时我以为,这次故障已经处理得差不多了。

但继续往下查才发现,那条 SQL 只是最先暴露出来的问题。真正埋得更深的,是大量业务表长期锁定的统计信息。

现在再回头看,整条链路就完整了:

所以前面清理归档、处理 SQL,解决的是当时正在发生的故障;这次把 Locked Stats 清掉,并让 Oracle Auto Stats 重新接管,处理的才是后面还可能再次把问题带回来的那部分。

这套库后续也不需要再额外部署一套每天全 Schema Gather 的任务,Auto Stats 本身一直是正常的,真正需要保证的是:业务表不要再长期脱离它的维护范围。

到这里,从 +ARCH 被打满开始追的这次问题,才算真正收口。

解决一条慢 SQL,只能让故障停下来;把让优化器长期看错数据的问题彻底修掉,这个故障才算真正结束。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。