☰
Oracle审计日志撑爆SYSTEM表空间?从清理到自动保留实战
2026/10/20 5:29:28 网站建设 项目流程

如果你问一个长期维护Oracle生产库的运维人员,最不想在凌晨被什么告警吵醒,“表空间无法扩展”绝对排得上号。我前阵子刚处理过一单:某客户业务库凌晨突然报ORA-01653 unable to extend table SYS.AUD$ by 8 in tablespace SYSTEM,数据库文件自动扩展已经打开,但SYSTEM表空间还是被彻底塞满了。登进去一看,祸根正是Oracle审计日志——SYS.AUD$这张审计表单独占了SYSTEM表空间接近90%的容量,里面躺着几千万条历史记录,最早的数据能追到三年前。

审计功能开着,扩容也开着,就是没有人想过日志会越攒越多、最终把系统表空间撑爆。这种情况在真实运维环境里太常见了。本文就把Oracle审计日志从排查、清理到建立自动保留机制讲透,重点回答三个问题:审计记录到底存在哪里?怎么安全清理?如何防止下次再爆。

1. 审计日志不清理,比不开审计还要折腾

1.1 一个典型的表空间告警背后

客户报障时给出了完整的Alert日志片段:

ORA-01653: unable to extend table SYS.AUD$ by 8 in tablespace SYSTEM

这种报错字面意思很清楚:SYS.AUD$想在SYSTEM表空间里继续扩展,但SYSTEM表空间已经没有可用空间了。需要说明的是,此时就算数据文件开启了AUTOEXTEND ON,如果已经触达MAXSIZE上限,或者表空间里散落的小碎片拼不出足够连续的区,一样会报错。

我当时的排查路径很直接:

-- 1. 看SYSTEM表空间整体使用情况 SELECT tablespace_name, file_name, bytes/1024/1024/1024 AS size_gb, maxbytes/1024/1024/1024 AS max_gb, autoextensible FROM dba_data_files WHERE tablespace_name = 'SYSTEM'; -- 2. 看哪个段占的空间最高 SELECT owner, segment_name, segment_type, bytes/1024/1024/1024 AS gb FROM dba_segments WHERE tablespace_name = 'SYSTEM' ORDER BY bytes DESC;

结果毫无悬念,前几条全部是SYS下的对象,排第一的就是SYS.AUD$,90多GB。再核对审计参数:

SHOW PARAMETER AUDIT_TRAIL

返回DB。也就是说,这个库开启的是传统数据库审计,所有审计记录都在写入SYS.AUD$数据字典表,而这张表的默认归属表空间正是SYSTEM。

这里想多说一句:很多团队对审计的态度是“开了就行”。安全合规检查时确认AUDIT_TRAIL参数不为NONE,就觉得尽到责任了。但没人统计过每天写入多少条、保留多久、磁盘够不够。审计数据不像业务数据那样会有人关注增长速度,它天生就是“只增不减、越攒越肥”的类型,最后往往以一次凌晨告警的方式让你记住它。

1.2 “开了审计就不管”的代价

审计日志堆积带来的问题不止是 SYSTEM 表空间告警,实际工作中我见过更麻烦的连锁反应:

  • 审计表写入等待变长,数据库出现enq: TX - row lock contention或log file sync异常,业务卡顿。
  • 归档日志暴涨。如果用DELETE方式清理历史审计记录,产生的 Redo 量甚至比正常业务写入还大。
  • 闪回区或归档目录被塞满,备库同步滞后。
  • SYSTEM表空间被审计数据占光后,其他依赖SYSTEM的小对象(例如回滚段、在线帮助表)扩展失败,整个实例变得不稳定。

所以,清理审计日志不是“可选优化”,而是开启审计功能后必须配套的基本运维工作。接下来的内容,我会从存储结构讲起,再给出真正可落地的清理方法。

2. 先别急着清:搞清楚审计记录存在哪三个地方

不同版本、不同参数配置下,Oracle审计日志的落点完全不同。很多人一上来就想TRUNCATE TABLE SYS.AUD$,但实际上你在12c及更高版本里有可能清错地方,甚至连SYS.AUD$都不是当前审计数据的真实载体。我习惯先做一次“三选一”的判断。

2.1 传统数据库审计:SYS.AUD$ 与 AUDIT_TRAIL=DB

传统审计模式下,当AUDIT_TRAIL参数设置为DB或DB,EXTENDED时,审计记录写入数据字典表SYS.AUD$。这张表是普通堆表,通常通过DBA_AUDIT_TRAIL视图查询。

关键特征是:

  • 11g及更早版本,SYS.AUD$默认存放在SYSTEM表空间,这也是“审计日志撑爆SYSTEM”最常见的原因。
  • 审计记录量取决于审计策略。如果AUDIT SESSION开启,审计所有登录,登录频繁的库一天就能产生上百万条记录。
  • 数据文件如果开启了自动扩展,表会在SYSTEM里不断涨,直到磁盘满或遇到上限。

判断当前是否走这个模式,可以直接看参数:

SELECT name, value FROM v$parameter WHERE name = 'audit_trail';

只要返回值是DB/DB,EXTENDED,且统一审计二进制未启用,那么清理的主要对象就是SYS.AUD$。

2.2 操作系统审计:adump目录里的海量文件

另一种常见配置是AUDIT_TRAIL=OS或AUDIT_TRAIL=XML。此时审计记录不再写入SYS.AUD$,而是落在操作系统文件里,目录由参数AUDIT_FILE_DEST指定,通常默认在$ORACLE_BASE/admin/$ORACLE_SID/adump下。

查看目录:

SHOW PARAMETER AUDIT_FILE_DEST

这个目录里的文件名形如SID_ora_进程号_序号.aud或.xml。数据库每个后台进程、每次连接都可能生成一个审计文件,高并发系统的adump目录一天产生上万个文件一点都不夸张。文件本身不大,但文件数量会把文件系统的inode耗尽,这也是一个容易被忽略的坑,后面我会单独讲。

清理OS审计文件不能靠一条简单的delete from,而是要用操作系统命令按时间批量处理。

2.3 统一审计:AUDSYS表空间与 UNIFIED_AUDIT_TRAIL

12c开始,Oracle引入了统一审计(Unified Auditing)。启用后,传统SYS.AUD$不再承担新的审计写入,所有审计记录统一进入AUDSYS.AUD$UNIFIED表,存放在AUDSYS表空间,查询入口是UNIFIED_AUDIT_TRAIL视图。

确认统一审计是否激活:

SELECT value FROM v$option WHERE parameter = 'Unified Auditing';

返回TRUE表示已启用。12c及以后的版本中,即使你看到AUDIT_TRAIL参数仍然是DB,也不能想当然地认为数据还在SYS.AUD$里,必须先确认统一审计状态。

三种落点对比如下:

模式存储载体查询视图清理手段
传统DB审计SYS.AUD$表DBA_AUDIT_TRAILTRUNCATE/DELETE/DBMS_AUDIT_MGMT
OS审计adump目录文件无,直接看OS文件find按mtime删除归档
统一审计AUDSYS.AUD$UNIFIEDUNIFIED_AUDIT_TRAILDBMS_AUDIT_MGMT专用流程

判断清楚落点之前,不要执行任何清理动作。清错对象不是最可怕的,最可怕的是清完才发现真正想保留的证据还在另一个地方。

3. 一轮完整的审计清理实操:从确认水位到清完验证

3.1 先量一量审计数据占了多少空间

无论哪种模式,第一步都是量化现状。我会同时看几个维度:审计表/段大小、审计记录数、时间跨度、所在表空间剩余量。

传统审计下的批量检查脚本:

-- AUD$ 段占用 SELECT owner, segment_name, bytes/1024/1024/1024 AS seg_gb FROM dba_segments WHERE segment_name = 'AUD$' AND owner = 'SYS'; -- 审计记录统计 SELECT COUNT(*) AS audit_cnt, MIN(ntimestamp#) AS earliest, MAX(ntimestamp#) AS latest FROM sys.aud$;

统一审计下的检查:

-- AUDSYS 表空间占用 SELECT tablespace_name, SUM(bytes)/1024/1024/1024 AS used_gb FROM dba_segments WHERE owner = 'AUDSYS' GROUP BY tablespace_name; -- 审计记录统计 SELECT COUNT(*) AS unified_cnt, MIN(event_timestamp) AS earliest, MAX(event_timestamp) AS latest FROM unified_audit_trail;

同时不要忘了看所在表空间:

SELECT a.tablespace_name, a.used_gb, b.max_gb FROM (SELECT tablespace_name, SUM(bytes)/1024/1024/1024 AS used_gb FROM dba_data_files GROUP BY tablespace_name) a LEFT JOIN (SELECT tablespace_name, SUM(maxbytes)/1024/1024/1024 AS max_gb FROM dba_data_files GROUP BY tablespace_name) b ON a.tablespace_name = b.tablespace_name;

这一步不是为了满足好奇心,而是用来决定后续清理策略:数据量多大,REDO压力会是怎样,是否需要分批,需不需要先备份归档。

3.2 历史数据没保留必要:TRUNCATE SYS.AUD$ 的正确姿势

如果判定历史审计记录没有保留价值,最干净的清理方式就是直接截断表:

TRUNCATE TABLE sys.aud$;

需要注意的细节非常多:

  1. TRUNCATE是DDL,不可回滚。清理前必须确认业务和安全合规真的允许丢掉全部历史记录。
  2. 执行前记录一下当前审计数:SELECT COUNT(*) FROM sys.aud$;,清完后再次计数确认归零。
  3. TRUNCATE之后,SYS.AUD$段的高水位会重置,但表空间文件大小不一定马上缩小。释放出来的空间会回到表空间内部供其他段复用,这对缓解SYSTEM表空间压力是有意义的。
  4. 检查相关索引状态:
SELECT index_name, status FROM dba_indexes WHERE table_name = 'AUD$' AND owner = 'SYS';

正常情况TRUNCATE不会删除索引,但如果出现UNUSABLE,需要重建:

ALTER INDEX sys.i_aud1 REBUILD;
  1. 清理后建议收集统计信息,避免后续写入时执行计划异常:
EXEC dbms_stats.gather_table_stats(ownname => 'SYS', tabname => 'AUD$', cascade => TRUE);

3.3 想保留近期数据:不要用DELETE全删历史

很多场景不能全清:等保、合规或内部审计要求必须保留最近180天或365天的记录。有些运维同学会写一条简单的DELETE:

DELETE FROM sys.aud$ WHERE ntimestamp# < SYSTIMESTAMP - INTERVAL '180' DAY;

这条SQL本身没错,但在几千万行的表上执行,后果远比你想象的严重。DELETE会产生海量Redo和Undo,每个被删除的行都会在Redo里留下完整记录,删2000万行,日志切换速度可以直接压垮归档目录,也容易把UNDO表空间撑大。备库同步延迟也会急剧拉大。

我踩过这个坑之后,总结出两套相对安全的保留式清理方案:

方案一:分批删除,严格控制每批删除量。

-- 每批最多删除50000行,循环执行 DECLARE v_count PLS_INTEGER := 1; BEGIN WHILE v_count > 0 LOOP DELETE FROM sys.aud$ WHERE ntimestamp# < SYSTIMESTAMP - INTERVAL '180' DAY AND ROWNUM <= 50000; v_count := SQL%ROWCOUNT; COMMIT; END LOOP; END; /

这种写法能避免一个超长事务,但循环中的反复扫描依然会让CPU和I/O有一定压力,建议在低峰期执行,并实时观察Redo切换频率。

方案二:备份要保留的数据,全清后回插(我目前更常用)。

-- 1. 把近期数据另存到临时表 CREATE TABLE sys.aud$_backup_2025 AS SELECT * FROM sys.aud$ WHERE ntimestamp# >= SYSTIMESTAMP - INTERVAL '180' DAY; -- 2. 截断原表 TRUNCATE TABLE sys.aud$; -- 3. 回插最新数据(这一步还会产生Redo,但量远小于全历史删除) INSERT INTO sys.aud$ SELECT * FROM sys.aud$_backup_2025; COMMIT;

这种方法最大的好处是:控制回插规模,历史大批量数据的删除不产生额外Redo。前提是SYSTEM表空间要有足够空间存放临时备份表。如果你的SYSTEM已经接近爆满,那就先把备份表放到别的表空间。

3.4 统一审计环境的专用清理流程

统一审计环境千万不要去手动TRUNCATE TABLE AUDSYS.AUD$UNIFIED,路径不对且容易引发故障。官方支持的清理套路是DBMS_AUDIT_MGMT包。

第一步,初始化清理配置(通常只需执行一次):

BEGIN DBMS_AUDIT_MGMT.INIT_CLEANUP( audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED, default_cleanup_interval => 72 ); END; /

第二步,设置要保留的“最后归档时间点”。比如保留30天:

BEGIN DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP( audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED, last_archive_time => SYSTIMESTAMP - INTERVAL '30' DAY ); END; /

第三步,执行清理:

BEGIN DBMS_AUDIT_MGMT.CLEAN_AUDIT_TRAIL( audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED, use_last_arch_timestamp => TRUE ); END; /

use_last_arch_timestamp设为TRUE,表示只清理早于第二步设置时间点的记录;如果设为FALSE,则不分时间全部清理。统一审计的清理动作是由数据库后台清理任务执行的,执行速度可能没有直接TRUNCATE那么快,但胜在安全和可控。

传统审计同样可以使用这套包,只需把audit_trail_type换成DBMS_AUDIT_MGMT.AUDIT_TRAIL_AUD_STD。

3.5 清理OS审计文件与inode释放

针对adump目录,我在生产上是这么处理的:

先统计文件数量和占用:

find $ORACLE_BASE/admin/$ORACLE_SID/adump -type f | wc -l du -sh $ORACLE_BASE/admin/$ORACLE_SID/adump df -i $ORACLE_BASE

然后删除90天前的文件:

find $ORACLE_BASE/admin/$ORACLE_SID/adump -type f -name "*.aud" -mtime +90 -print -delete

如果怀疑有进程仍持有已删除文件句柄,删除前先mv到备份目录:

mkdir -p /tmp/adump_archive find $ORACLE_BASE/admin/$ORACLE_SID/adump -type f -name "*.aud" -mtime +90 -exec mv {} /tmp/adump_archive/ \;

等确认一切正常,再清理备份目录。这个习惯能避免“磁盘空间没释放”的尴尬。OS审计文件虽然单文件小,但量级上来后对文件系统inode的消耗远超磁盘空间,务必重视。

清理完以后,还要顺手确认统一审计和传统审计模式下各自清理后的记录数:

SELECT COUNT(*) FROM sys.aud$; SELECT COUNT(*) FROM unified_audit_trail;

不要只清不验,清理任务最忌讳“执行成功但没生效”。

4. 别等下次爆了再清:审计策略与自动化保留机制

4.1 先回答三个问题:审什么、存哪里、留多久

清理审计日志的终极方案不是“爆了再清”,而是在设计端控制审计数据量。做审计策略前,我通常拉着业务和安全管理确认三件事。

审什么:很多库开启的是全量审计,所有会话、所有操作全部记录。但真实业务根本不需要这么细。建议只审计登录失败、DDL变更、权限授予回收、敏感表DML等关键动作。例如:

AUDIT SESSION WHENEVER NOT SUCCESSFUL; AUDIT CREATE ANY TABLE, DROP ANY TABLE, ALTER ANY TABLE; AUDIT GRANT ANY PRIVILEGE;

存哪里:优先让审计数据放进独立表空间,和SYSTEM表空间彻底隔离。理想状态是给审计表单独划分一块盘,即使日志暴涨,也只是审计表空间报警,业务核心表空间不受牵连。

留多久:根据实际合规要求确定保留周期。常见是90天、180天、365天。定好周期后写进自动化清理任务,不要靠“记得手动清了”这种不靠谱的方式。

4.2 用DBMS_AUDIT_MGMT做自动清理

统一审计模式下,初始化清理配置后,Oracle本身会建立一个后台清理任务,按照你设定的default_cleanup_interval周期性执行。但很多生产环境对Oracle自带任务的执行时间不满意,更习惯自己掌控。

我推荐用DBMS_SCHEDULER建一个每天凌晨执行的清理Job,脚本如下:

BEGIN DBMS_SCHEDULER.CREATE_JOB( job_name => 'PURGE_AUDIT_LOG_JOB', job_type => 'PLSQL_BLOCK', job_action => ' BEGIN DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP( audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED, last_archive_time => SYSTIMESTAMP - INTERVAL ''90'' DAY ); DBMS_AUDIT_MGMT.CLEAN_AUDIT_TRAIL( audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED, use_last_arch_timestamp => TRUE ); END;', start_date => TRUNC(SYSDATE) + INTERVAL '2' HOUR, repeat_interval => 'FREQ=DAILY; BYHOUR=2; BYMINUTE=0; BYSECOND=0', enabled => TRUE, comments => 'daily purge unified audit log before 90 days' ); END; /

对于传统SYS.AUD$,同样可以把清理动作封装进Job,但要注意传统模式下DBMS_AUDIT_MGMT对SYS.AUD$的清理是基于时间戳执行的,逻辑一致。

如果版本较低或不想依赖这部分功能,可以退而求其次:写一个存储过程,判断当天日期属于月初/季度初,然后调用前面提到的分批删除方案。自动化不是越复杂越好,可靠才是第一位的。

4.3 把SYS.AUD$从SYSTEM表空间挪走的迁移方案

如果你的库还是传统审计模式,且SYS.AUD$还在SYSTEM表空间,我最强烈的一个建议是:趁早把它迁走。这一步可以彻底解决“SYSTEM被审计日志撑爆”的结构性问题。

迁移前先创建一个独立表空间,假设叫AUD_TBS:

CREATE TABLESPACE aud_tbs DATAFILE '/oradata/ORA19C/aud_tbs01.dbf' SIZE 10G AUTOEXTEND ON NEXT 1G MAXSIZE 100G;

如果你的数据库版本支持DBMS_AUDIT_MGMT.SET_AUDIT_TRAIL_LOCATION(12c及以上通常可用),则可以这样迁移:

BEGIN DBMS_AUDIT_MGMT.SET_AUDIT_TRAIL_LOCATION( audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_AUD_STD, audit_trail_location => 'AUD_TBS' ); END; /

这个操作会把SYS.AUD$迁移到新表空间。执行时我建议先做好备份,并预留足够空间,因为迁移过程中会重建表结构。11g等较老版本如果不能用这个接口,需要借助其他方式,但风险偏高,建议直接在迁移窗口内确认方案后在测试库演练再实施。

统一审计模式下,AUDSYS表空间本身是独立的,一般不需要再迁移。但还是要监控它的使用量,不要因为名字里不带SYSTEM就觉得它不会满。

4.4 日常巡检SQL与告警阈值建议

我建议把下面的SQL放进日常巡检,每两天看一次:

SELECT 'AUD$' AS obj_type, COUNT(*) AS total_records, ROUND(SUM(bytes)/1024/1024/1024, 2) AS seg_gb FROM dba_segments, sys.aud$ WHERE segment_name = 'AUD$' AND owner = 'SYS' GROUP BY segment_name UNION ALL SELECT 'UNIFIED_AUDIT' AS obj_type, (SELECT COUNT(*) FROM unified_audit_trail), (SELECT ROUND(SUM(bytes)/1024/1024/1024, 2) FROM dba_segments WHERE owner = 'AUDSYS');

更简单的方式是监控表空间使用率,阈值定在70%预警、85%告警。如果审计表所在表空间使用率连续两天超过70%,就说明当前审计增长速率和清理频率不匹配,需要调整保留天数或加大清理频率。

5. 这些坑我是怎么避开的:几种典型失误复盘

5.1 DELETE清理造成Redo/Undo风暴的那一次

有一年处理某客户库,审计数据接近3000万条,需要保留90天,其余全删。当时我图省事直接执行了一条大DELETE,结果执行了快两个小时,在线日志切换频率从正常的半小时一次变成几分钟一次,归档目录一度达到95%,备库延迟从0涨到几个小时。那次教训让我彻底明白了:审计表不是业务表,它处于“高频写入、低频删除”的状态,删除动作产生的日志量常常比删除的数据本身还可怕。

后来我把策略改为“备份近期数据,TRUNCATE后回插”,或者严格分批删除,并且事先估算AUD$记录数和近7天增长量,再决定分批大小。

5.2 adump目录把inode耗尽

另一个客户环境,OS审计文件数量达到几十万,数据文件所在分区df -h显示才用了60%,但应用突然报无法创建临时文件。查df -i才发现inode已经100%。这种故障非常隐蔽,磁盘容量看着充足,实际文件系统索引节点用完了。

处理方法是清理过期文件,并且我建议从此给adump目录设置独立的定时清理任务,保留周期和数据库审计保留周期保持一致:

find $ORACLE_BASE/admin/$ORACLE_SID/adump -type f -name "*.aud" -mtime +90 -delete

同时也提醒:如果AUDIT_TRAIL=OS在12c+已经很少见,尽量切换为统一审计,少一个文件系统的管理维度。

5.3 闪回开启状态下,裸TRUNCATE等于销毁证据

有朋友问过我:开了闪回数据库,TRUNCATE审计表以后好歹还能用闪回查询捞回来一点吧?答案是捞不回来的。TRUNCATE是DDL,不完全生成UNDO,闪回查询对已经被TRUNCATE重置的数据基本无能为力。如果审计记录本身承担着“安全证据”的角色,裸TRUNCATE就相当于手动销毁证明,这在合规审计视角下是无法接受的。

我现在面对“全清”需求时,一定会先和需求方确认:真的没有保留义务吗?如果拿不准,就先把需要保留的数据导出到外部表或单独备份,再执行清理。多花半小时做备份,好过事后被问“你凭什么把记录删了”。

5.4 我的固定执行窗口与双人复核习惯

清理审计日志虽然不是什么高难度操作,但我依然会把它当作变更流程来走。固定动作包括:

  • 选择凌晨低峰期执行,避开业务跑批和整库备份窗口。
  • 执行前记录审计行数、表空间使用率、归档目录剩余空间。
  • 执行后30分钟内观察Alert日志、Redo切换频率、归档产生速率。
  • 双人复核:一个人执行,另一个人核对清理前后计数和表空间水位变化。

这套流程看起来朴素,但确实帮我避免过不少次“清理成功后才发现表空间还是满的”的乌龙——有些时候审计表清掉了,但表空间数据文件本身已经被自动扩展到很大,操作系统磁盘依旧紧张,这时候还要单独评估是否需要收缩数据文件或拆分数据文件到新存储。

清理Oracle审计日志这件事,本质上不是“会不会TRUNCATE”的问题,而是你怎么看待审计数据的生命周期。它的产生是无状态的、持续的,它的清理必须是有策略的、自动化的。把存储位置理清、清理手段选对、保留周期定好、巡检项写上,这四件事做完,以后再听到类似告警的概率就会低很多。我在实际项目中还会把审计增长量作为季度容量评估的一个常规指标,提前预估未来三个月的空间需求。如果你正在被审计日志膨胀困扰,建议这个月就按上面的步骤做一次彻底盘点:清掉历史包袱,再把自动清理机制建起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询