Oracle Auto Stats 明明正常,为什么 519 张表一年没更新?
前言
前面我们已经写了 3 篇文章来讨论过这个问题:
- 凌晨 1 点 Oracle 宕机:1TB 归档盘怎么突然就满了?
- 一条 SELECT,为什么能让 Oracle 产生 384GB Redo?
- 从 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_BATCH 和 BIZ_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-20,STATTYPE_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_STATS、LOCK_TABLE_STATS、GATHER_TABLE_STATS、GATHER_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_ROWS、BLOCKS
和实际 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,只能让故障停下来;把让优化器长期看错数据的问题彻底修掉,这个故障才算真正结束。
- 点赞
- 收藏
- 关注作者
评论(0)