凌晨 1 点 Oracle 宕机:1TB 归档盘怎么突然就满了?
“100 条命令”系列已经写了 38 篇,接下来继续写 RAC、Data Guard、GoldenGate 这些专题。想接着看,可以收藏 ORA100 · DBA100,微信里搜索小程序 「三笠的百令册」。

网站地址:
昨晚夜里 1 点多被叫醒了,有一套 Oracle 19c RAC 数据库出问题了,业务反馈连接数据库报错,起来之后看了一下发现是 ASM 归档磁盘组 +ARCH 满了,比较奇怪的是平常这个库归档日志每天只有几十 GB 的增长,保留 7 天,怎么 1TB 会 这么快用完呢?
本文记录一下完整的分析过程以及解决方案,希望对大家有所帮助。
问题分析
首先切换到 grid 用户,查看 ASM 磁盘组的使用情况:
[grid@SSTHLMESDB01:/home/grid]$ asmcmd lsdg
State Type Rebal Sector Logical_Sector Block AU Total_MB Free_MB Req_mir_free_MB Usable_file_MB Offline_disks Voting_files Name
MOUNTED EXTERN N 512 512 4096 4194304 1049040 432 0 432 0 N ARCH/
MOUNTED EXTERN N 512 512 4096 4194304 6294240 3637144 0 3637144 0 N DATA/
MOUNTED NORMAL N 512 512 4096 4194304 62940 61888 20980 20454 0 Y OCR/
可以看到 ARCH 归档磁盘组总大小大约 1TB,只剩 432MB 可用,基本已经贴着 100% 了。
因为是大半夜,所以我的第一反应当然是先释放空间,避免数据库因为无法归档继续出问题:
rman target /
delete noprompt archivelog until time 'sysdate-3';
空间处理完之后,业务那边反馈已经恢复正常。但是更重要的问题是:归档为什么突然能把 1TB 的 ASM 磁盘组打满?
我们可以先从 ASM 里面看:
ASMCMD [+] > cd arch
ASMCMD [+arch] > cd orcl
ASMCMD [+arch/orcl] > cd ARCHIVELOG
ASMCMD [+arch/orcl/ARCHIVELOG] > ls
2026_09_13/
2026_09_14/
2026_09_15/
2026_09_16/
2026_09_17/
2026_09_18/
ASMCMD [+arch/orcl/ARCHIVELOG] > du 2026_09_18/
Used_MB Mirror_used_MB
448328 448328
单看这一天,ASM 上已经有大约 438 GB,这明显是不正常的。
接下来从数据库里统计每天归档量:
SELECT TO_CHAR(first_time,'YYYY-MM-DD') day,
ROUND(
SUM(bytes)/1024/1024/1024,
2
) arch_gb,
COUNT(*) arch_count
FROM (
SELECT thread#,
sequence#,
TRUNC(first_time) first_time,
MAX(blocks * block_size) bytes
FROM gv$archived_log
WHERE first_time >= DATE '2026-09-12'
AND first_time < DATE '2026-09-19'
AND dest_id = 1
AND archived = 'YES'
GROUP BY thread#,
sequence#,
TRUNC(first_time)
)
GROUP BY TO_CHAR(first_time,'YYYY-MM-DD')
ORDER BY day;
DAY ARCH_GB ARCH_COUNT
------------ ---------- ----------
2026-09-12 40.73 57
2026-09-13 57.14 80
2026-09-14 57.71 84
2026-09-15 132.59 184
2026-09-16 77.30 108
2026-09-17 262.28 376
2026-09-18 450.57 654
正常应该是每天几十 GB,但是从 9.14 号开始,归档开始异常增长,接下来需要搞清楚的就是:这些归档是不是数据库真的产生了这么多 Redo?
众所周知,归档文件最终来源于在线重做日志(online redo),所以比单看归档文件更直接的办法,是看数据库实际产生了多少 redo size。
从 DBA_HIST_SYSSTAT 取 AWR 中的 redo size,按照实例和 startup 时间做差,同时避免实例重启后累计值 reset 产生负数:
SET LINES 200
SET PAGES 100
COL DAY FORMAT A12
COL REDO_GB FORMAT 999999.99
WITH x AS (
SELECT sn.instance_number,
sn.snap_id,
sn.end_interval_time,
st.value,
LAG(st.value) OVER (
PARTITION BY st.dbid,
st.instance_number,
sn.startup_time
ORDER BY st.snap_id
) prev_value
FROM dba_hist_sysstat st
JOIN dba_hist_snapshot sn
ON sn.snap_id = st.snap_id
AND sn.dbid = st.dbid
AND sn.instance_number = st.instance_number
WHERE st.stat_name = 'redo size'
AND sn.begin_interval_time >= DATE '2026-09-13'
AND sn.begin_interval_time < DATE '2026-09-19'
)
SELECT TO_CHAR(end_interval_time,'YYYY-MM-DD') day,
ROUND(
SUM(
CASE
WHEN prev_value IS NOT NULL
AND value >= prev_value
THEN value - prev_value
ELSE 0
END
) / 1024 / 1024 / 1024,
2
) redo_gb
FROM x
WHERE TRUNC(end_interval_time)
IN (DATE '2026-09-14',
DATE '2026-09-18')
GROUP BY TO_CHAR(end_interval_time,'YYYY-MM-DD')
ORDER BY day;
DAY REDO_GB
------------ ----------
2026-09-14 55.29
2026-09-18 445.89
这样一看就比较清楚了,9 月 18 日:
AWR redo size 445.89GB
Archive 450.57GB
ASM目录 约438GB
再看 redo entries:
SET LINES 200
COL DAY FORMAT A12
COL REDO_ENTRIES FORMAT 999999999999999999
WITH x AS (
SELECT sn.instance_number,
sn.snap_id,
sn.end_interval_time,
st.value,
LAG(st.value) OVER (
PARTITION BY st.dbid,
st.instance_number,
sn.startup_time
ORDER BY st.snap_id
) prev_value
FROM dba_hist_sysstat st
JOIN dba_hist_snapshot sn
ON sn.snap_id = st.snap_id
AND sn.dbid = st.dbid
AND sn.instance_number = st.instance_number
WHERE st.stat_name = 'redo entries'
AND sn.begin_interval_time >= DATE '2026-09-13'
AND sn.begin_interval_time < DATE '2026-09-19'
)
SELECT TO_CHAR(end_interval_time,'YYYY-MM-DD') day,
SUM(
CASE
WHEN prev_value IS NOT NULL
AND value >= prev_value
THEN value-prev_value
ELSE 0
END
) redo_entries
FROM x
WHERE TRUNC(end_interval_time)
IN (DATE '2026-09-14',
DATE '2026-09-18')
GROUP BY TO_CHAR(end_interval_time,'YYYY-MM-DD')
ORDER BY day;
DAY REDO_ENTRIES
------------ -------------------
2026-09-14 105820854
2026-09-18 535963669
也就是说:
redo size:55.29GB → 445.89GB,约 8.06 倍
redo entries:1.058亿 → 5.360亿,约 5.07 倍
分析到这里,思路更清晰了:为什么数据库一天产生了 445.89GB Redo?
正常情况下,9 月 12 ~ 14 日每天只有四五十 GB:
09-12 40.73GB Archive
09-13 57.14GB Archive
09-14 57.71GB Archive
↓
09-17 262.28GB
09-18 450.57GB
接下来真正要查的,这些多出来的 Redo 到底是谁产生的?
通常碰到 Redo 暴涨,我会先想到大批量 INSERT、UPDATE、DELETE,或者某个 ETL、数据补偿、批处理任务突然放量。
这套库里也确实很快查到了几批非常大的 DML:
SET LINES 300
SET PAGES 200
COL SQL_ID FORMAT A13
COL MODULE FORMAT A25
COL CMD FORMAT A8
COL EXEC_14 FORMAT 999999999999
COL EXEC_18 FORMAT 999999999999
COL ROWS_14 FORMAT 999999999999
COL ROWS_18 FORMAT 999999999999
COL DIFF_ROWS FORMAT 999999999999
COL RATIO FORMAT 999999.99
COL SQL_TEXT FORMAT A100
WITH sql_base AS (
SELECT s.sql_id,
MAX(s.module) module,
MAX(DBMS_LOB.SUBSTR(t.sql_text, 1000, 1)) sql_text,
SUM(CASE
WHEN TRUNC(sn.begin_interval_time) = DATE '2026-09-14'
THEN s.executions_delta
ELSE 0
END) exec_14,
SUM(CASE
WHEN TRUNC(sn.begin_interval_time) = DATE '2026-09-18'
THEN s.executions_delta
ELSE 0
END) exec_18,
SUM(CASE
WHEN TRUNC(sn.begin_interval_time) = DATE '2026-09-14'
THEN s.rows_processed_delta
ELSE 0
END) rows_14,
SUM(CASE
WHEN TRUNC(sn.begin_interval_time) = DATE '2026-09-18'
THEN s.rows_processed_delta
ELSE 0
END) rows_18
FROM dba_hist_sqlstat s
JOIN dba_hist_snapshot sn
ON sn.snap_id = s.snap_id
AND sn.dbid = s.dbid
AND sn.instance_number = s.instance_number
JOIN dba_hist_sqltext t
ON t.dbid = s.dbid
AND t.sql_id = s.sql_id
WHERE sn.begin_interval_time >= DATE '2026-09-14'
AND sn.begin_interval_time < DATE '2026-09-19'
AND TRUNC(sn.begin_interval_time)
IN (DATE '2026-09-14', DATE '2026-09-18')
AND REGEXP_LIKE(
LTRIM(UPPER(DBMS_LOB.SUBSTR(t.sql_text,1000,1))),
'^(INSERT|UPDATE|DELETE|MERGE)'
)
GROUP BY s.sql_id
)
SELECT *
FROM (
SELECT sql_id,
module,
CASE
WHEN REGEXP_LIKE(LTRIM(UPPER(sql_text)), '^INSERT') THEN 'INSERT'
WHEN REGEXP_LIKE(LTRIM(UPPER(sql_text)), '^UPDATE') THEN 'UPDATE'
WHEN REGEXP_LIKE(LTRIM(UPPER(sql_text)), '^DELETE') THEN 'DELETE'
WHEN REGEXP_LIKE(LTRIM(UPPER(sql_text)), '^MERGE') THEN 'MERGE'
END cmd,
exec_14,
exec_18,
rows_14,
rows_18,
rows_18 - rows_14 diff_rows,
ROUND(
CASE
WHEN rows_14 > 0
THEN rows_18 / rows_14
END,
2
) ratio,
SUBSTR(sql_text,1,100) sql_text
FROM sql_base
WHERE rows_18 > rows_14
ORDER BY rows_18 - rows_14 DESC
)
WHERE ROWNUM <= 50;
有一张明细表,一天写了 5000 多万行;另一张表的 INSERT 从不到 60 万行突然涨到了 800 多万行。
初看确实觉得有可能是这些 sql 导致的,但是继续深入分析 redo size、redo entries、对象变化量以及业务 SQL 之后,发现数字上始终有一块对不上:这些 DML 确实增长了,但它们真的足以解释一天 445.89GB 的 Redo 吗?
真正的突破口,是后面查到的一个很少会在日常 Redo 排查里重点关注的统计项。再顺着它往下追,最后抓出来的核心 SQL 甚至不是 INSERT、UPDATE 或 DELETE。
而是一条:
SELECT ...
更反常识的是,这条 SELECT 最终和当天数百 GB 的 Redo 增长建立起了完整的证据链。
预告
下一篇继续:
《一条 SELECT,为什么能让 Oracle 产生 384GB Redo?》
从 445.89GB Redo 开始,一直查到 Lost Write Detection、78 亿次 Physical Reads,以及那条占全库约 85% Physical Reads 的 SQL。
更多数据库命令,见 ORA100 · DBA100:
也可以在微信搜索小程序 「三笠的百令册」。
- 点赞
- 收藏
- 关注作者
评论(0)