凌晨 1 点 Oracle 宕机:1TB 归档盘怎么突然就满了?

举报
Lucifer三思而后行 发表于 2026/09/19 14:01:34 2026/09/19
【摘要】 “100 条命令”系列已经写了 38 篇,接下来继续写 RAC、Data Guard、GoldenGate 这些专题。想接着看,可以收藏 ORA100 · DBA100,微信里搜索小程序 「三笠的百令册」。网站地址:昨晚夜里 1 点多被叫醒了,有一套 Oracle 19c RAC 数据库出问题了,业务反馈连接数据库报错,起来之后看了一下发现是 ASM 归档磁盘组 +ARCH 满了,比较奇怪的...

“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 暴涨,我会先想到大批量 INSERTUPDATEDELETE,或者某个 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 sizeredo entries、对象变化量以及业务 SQL 之后,发现数字上始终有一块对不上:这些 DML 确实增长了,但它们真的足以解释一天 445.89GB 的 Redo 吗?

真正的突破口,是后面查到的一个很少会在日常 Redo 排查里重点关注的统计项。再顺着它往下追,最后抓出来的核心 SQL 甚至不是 INSERTUPDATEDELETE

而是一条:

SELECT ...

更反常识的是,这条 SELECT 最终和当天数百 GB 的 Redo 增长建立起了完整的证据链。

预告

下一篇继续:

《一条 SELECT,为什么能让 Oracle 产生 384GB Redo?》

从 445.89GB Redo 开始,一直查到 Lost Write Detection、78 亿次 Physical Reads,以及那条占全库约 85% Physical Reads 的 SQL。

更多数据库命令,见 ORA100 · DBA100

也可以在微信搜索小程序 「三笠的百令册」

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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