项目标题: 重庆思庄oracle技术分享-oracle 审计日志清理
项目正文: 关于Oracle数据库审计日志清理的技术分享,主要讨论审计表空间膨胀、AUD$表清理、审计日志轮转等数据库运维问题。
关键词: Oracle审计, 审计日志清理, AUD$表, 审计表空间, 数据库运维
摘要描述: 分享Oracle数据库审计日志膨胀的排查思路、清理实操方法与长期治理方案。 干这行久了你会发现,Oracle数据库的告警十有八九绕不开两个词:表空间满了、归档满了。而这两个“满”背后,又有一大半的根源是同一个东西——审计日志。我刚接手某生产系统的时候,第一眼就被吓到了,一个纯OLTP业务库,AUD$表居然占了将近300GB,系统表空间差点被撑爆,数据库直接进入告警状态。后来花了一整个下午清理、复盘、调参数,才算把这只“吞空间的怪兽”按回去。
这篇东西就是冲着这个来的,把Oracle审计日志从膨胀原理、排查思路、清理实操到长期轮转方案一条龙讲清楚。不管你是刚接触Oracle的新手DBA,还是被审计日志折磨过的老运维,这篇都能给你一点可以直接抄作业的思路。
1. 审计日志为什么说涨就涨
1.1 审计日志的存储机制
很多初学者会把“审计”理解成简单地在告警日志里写几行字,实则不然。Oracle的审计体系在设计上就自带一张实打实的数据库表——SYS.AUD$,所有标准审计记录默认都往这张表里写。这张表在哪个表空间里,问题就在哪个表空间里炸。
Oracle开启审计的方式很直观,一套参数下来,符合条件的操作就会生成一条记录。举个例子:
-- 查看当前审计相关参数 SHOW PARAMETER AUDIT_TRAIL; SHOW PARAMETER AUDIT_SYS_OPERATIONS;AUDIT_TRAIL的值有几种:NONE表示关闭,OS表示写到操作系统审计文件,DB表示写入AUD$表,DB_EXTENDED则是在DB的基础上额外记录SQL文本和绑定变量。一旦设置为DB或DB_EXTENDED,所有被审计的操作都会往AUD$表里塞数据。如果审计策略还比较激进,比如审计了所有登录、所有DDL、所有DML,那这张表的膨胀速度就不会跟你商量了。
1.2 审计膨胀的典型推手
结合我实际碰到的情况,审计日志“失控”最常见的几类原因:
- 审计策略过宽。比如AUDIT ALL BY某用户,甚至直接对全库开启细粒度审计(FGA),每一个普通查询都会落一条记录。
- 应用连接池频繁重建连接。很多Java应用用了连接池,但如果池参数配得不合理,每隔一会儿就重新登录一次,每次登录都会被审计,记录量直接翻倍。
- 批量任务集中在夜间跑批,跑批期间产生的审计记录占全天的大头。
- 长期不清理的“历史遗留”。AUD$表本来就有几十GB,清理策略一直没跟上,老账新账叠一起。
用一句话概括:审计日志失控的本质,是写入速度和清理速度长期不匹配,最后撑爆表空间。你如果现在打开数据库发现SYSTEM表空间使用率超过了90%,第一反应真不应该是“扩表空间”,而是先查AUD$表占地多少,这是我在实战中血泪换来的经验。
1.3 什么时候该清、什么时候该忍
不是说审计日志一涨就要立刻动手。你得先判断数据库当前的状态:
- 如果表空间使用率在80%以下,且增长速度平稳,可以先观察,同时准备清理方案。
- 如果表空间使用率已经超过90%,甚至报警了,没得选,立刻启动紧急清理流程。
- 如果AUD$表所在表空间是SYSTEM(默认就是),那就更危险,因为SYSTEM表空间还承担着数据字典等重要功能,它满了不仅是写不了审计日志的问题,整个库都可能出状况。
碰到这种情况,我建议你优先确认一件事:数据库是否开启了审计。有些库可能只是默认开启的强制审计(比如SYS用户的某些操作),AUD$表虽然增长慢,但常年累月也会膨胀,不要被“我们没开审计”这种说法带偏,先查参数再下结论。
提示:拿到一个告警库,先跑一句
SELECT * FROM DBA_AUDIT_TRAIL WHERE ROWNUM < 5;看看有没有数据,再查SELECT COUNT(*) FROM SYS.AUD$;评估量级,比拍脑袋靠谱多了。
2. 清理前的三查三看
2.1 查大小、查增长、查分布
动手清理之前,先花几分钟把“敌情”摸清楚。你连AUD$表多大都不知道就贸然TRUNCATE,我见过有人直接把整个SYSTEM表空间搞成只读的,那才是真事故。
核心查询就三句:
-- 查看AUD$表占用了多少空间 SELECT SEGMENT_NAME, BYTES/1024/1024/1024 AS SIZE_GB FROM DBA_SEGMENTS WHERE SEGMENT_NAME IN ('AUD$', 'FGA_LOG$'); -- 查看审计记录总量及最早/最晚记录时间 SELECT COUNT(*), MIN(NTIMESTAMP#), MAX(NTIMESTAMP#) FROM SYS.AUD$; -- 按天统计审计记录量 SELECT TO_CHAR(NTIMESTAMP#, 'YYYY-MM-DD') AS DAY, COUNT(*) FROM SYS.AUD$ GROUP BY TO_CHAR(NTIMESTAMP#, 'YYYY-MM-DD') ORDER BY DAY DESC;注意,审计表里除了AUD$,还有FGA_LOG$(细粒度审计日志表)。很多DBA只盯着AUD$,结果FGA_LOG$在SYSTEM表空间里偷偷长到几十GB,这属于典型的“灯下黑”。
2.2 查审计策略,找到膨胀源头
清理是一次性的,但如果你不把源头堵住,过两周又得再清一回。我每次都会查当前启用的审计策略,看看到底是谁在“制造垃圾”:
-- 查看数据库级统一审计策略(Oracle 12c及以上) SELECT POLICY_NAME, AUDIT_OPTION, CONDITION_EVAL_OPT FROM AUDIT_UNIFIED_ENABLED_POLICIES; -- 查看传统审计设置 SELECT AUDIT_OPTION, SUCCESS, FAILURE FROM DBA_STMT_AUDIT_OPTS; -- 查看FGA策略 SELECT OBJECT_SCHEMA, OBJECT_NAME, POLICY_NAME, ENABLED FROM DBA_AUDIT_POLICIES;这步的意义在于:你要分清哪些审计是合规要求必须保留的,哪些是当初调试遗留的。我遇到过有个系统,整整三年前为了排查某个问题开了一条“审计所有DML”的策略,问题解决后策略忘了关,三年攒了200GB的审计记录,就问你夸张不夸张。
2.3 查归档与备份策略,想清楚“后果”
清理AUD$表之前,必须想清楚:这些审计记录还需要保留吗?如果要保留,保留多久?这不仅是技术问题,还涉及合规。虽然我们不做政策评论,但审计日志在很多场景里就是“事后追溯”的依据。
实操层面我的建议是:
- 如果业务上有追溯需求,先做备份,比如把AUD$表按时间分区抽出来,EXPORT到历史表或外部文件,再做清理。
- 如果纯粹是垃圾数据,比如连“查询次数统计”这种毫无价值的记录都攒了好几年,直接清理没问题。
- 如果数据库开启了归档模式,且归档空间不容乐观,大规模DELETE会产生大量归档日志,这个账也要提前算。
注意:审计日志的清理不是越激进越好。频繁的大批量DELETE会带来UNDO膨胀、归档暴涨、索引碎片化等多米诺骨牌效应。后面我会详细拆解每种清理方式的利弊,你先别急着动手。
3. 三种主流清理方案逐一拆解
3.1 方案一:TRUNCATE——快刀斩乱麻
如果你确认审计记录不需要保留,或者已经完成了备份,最干净利落的方式就是TRUNCATE。
-- 清空AUD$表 TRUNCATE TABLE SYS.AUD$; -- 清空FGA日志表 TRUNCATE TABLE SYS.FGA_LOG$;为什么推荐TRUNCATE而不是DELETE?两个原因:
第一,TRUNCATE是DDL操作,不走UNDO,不产生UNDO膨胀;第二,TRUNCATE直接释放段空间(加上DROP STORAGE选项的话),表空间使用率立竿见影地降下来。DELETE则会产生大量UNDO和REDO,跑几个小时都有可能,期间还会把归档空间顶爆。
但TRUNCATE有前置条件:不能有外键引用、不能有正在进行的活动事务。实际操作中常见的问题是,某些系统会从AUD$表做数据抽取(ETL同步审计信息到大数据平台),如果有视图或者物化视图基于AUD$表刷新,TRUNCATE就可能报ORA-00054“资源正忙”。这种时候需要跟业务方确认一下,或者挑业务低峰期操作。
这段实操经验值得记下来:我习惯在TRUNCATE之前先做一次计数和空间占用快照,清理后对比次数和空间,方便跟领导汇报“清了多少、释放了多少”。干运维,干活是次要的,交代清楚才是核心。
3.2 方案二:分批DELETE——兼顾留痕与稳定
如果你的审计记录还有保留价值,比如需要留最近3个月的,那就不能一把TRUNCATE全清。这时候用分批DELETE是比较稳的方案。
核心思路:按时间范围,每次删一小批(比如5万条或10万条),然后提交,循环执行。这样做的好处是控制UNDO压力,避免一次删除百万级数据把回滚段撑爆。
-- 分批删除指定时间之前的审计记录,每批删除5万条 DECLARE V_COUNT NUMBER := 1; BEGIN WHILE V_COUNT > 0 LOOP DELETE FROM SYS.AUD$ WHERE NTIMESTAMP# < TO_TIMESTAMP('2024-01-01', 'YYYY-MM-DD') AND ROWNUM <= 50000; V_COUNT := SQL%ROWCOUNT; COMMIT; END LOOP; END; /这里有几个细节要特别注意:
不能只靠DELETE,删完还要重建表的高水位线,也就是执行
ALTER TABLE SYS.AUD$ MOVE;或者SHRINK SPACE,否则表空间使用率不会明显下降,只是表里的行变少了。很多人清完发现“空间没释放”,就是栽在这个地方。如果你不知道AUD$表怎么重建索引,至少要在MOVE之后重建一下索引,避免后续审计写入出现索引性能问题。标准操作是
ALTER INDEX SYS.AUDIT_PK REBUILD;这种依赖索引名的,需要先查一下实际索引名。删除过程中别忘了监控UNDO表空间使用率。如果UNDO空间紧张,请降低每批删除的行数,或者干脆换TRUNCATE方案。
为什么说这个方案适合“留痕”?因为你可以只把N个月以前的记录清掉,保留最近一段时间的审计数据。这个时间窗口根据各家业务诉求来定,有的留3个月,有的留半年,没有绝对标准,但建议跟安全团队确认清楚,别到时候需要追溯却拿不出数据,那就尴尬了。
3.3 方案三:切换新表再归档——生产环境的体面操作
TRUNCATE太粗暴,DELETE太耗时,那有没有一种方案既不影响业务、又能干干净净腾出空间的?
有:把AUD$表改成重定向到新表,旧表直接归档。这个方案尤其适合AUD$表已经几十GB乃至上百GB,用DELETE根本不现实,而你又不想承担TRUNCATE风险的大库。
思路是这样的:
- 先建一张新表AUD$NEW,结构和AUD$完全一致。
- 停止审计写入(如果可以接受短暂停审计的话),或者直接利用Oracle 12c+的审计表重定向功能。
Oracle从12.1开始就支持用DBMS_AUDIT_MGMT包做审计表的分区和归档,不过底层思路还是要理解。简单讲,就是用DBMS_AUDIT_MGMT.SET_AUDIT_TRAIL_LOCATION把审计表重定向到一个新表,然后老表就可以放心离线归档了。
实际操作我推荐一个“伪重定向”的手段,在生产上也验证过:
-- 创建与AUD$结构一致的新表 CREATE TABLE SYS.AUD$_BAK AS SELECT * FROM SYS.AUD$ WHERE 1=0; -- 通过同义词或表重命名方式切换(具体视版本而定) -- 注意:此操作极其敏感,必须先停审计或确认写入中断的容忍度重命名系统审计表这个操作,很多生产环境是不允许的。更稳妥的做法是:在业务低谷窗口,先用AUDIT_TRAIL='NONE'参数临时关闭审计或用NOAUDIT关掉具体策略,然后做TRUNCATE或MOVE,操作完成后再把审计打开。这种“临时闪断”的方式,比在审计表上做复杂手术要安全得多。
实操中我更常用的组合拳是:先确认能接受中断审计几分钟,然后临时停止审计写入,TRUNCATE AUD$表,重建索引,最后恢复审计。整个过程控制在几分钟内完成,系统表空间立刻瘦身,代价最小。
4. 清理实操中经常踩的坑
4.1 表被锁了,TRUNCATE直接卡住
这个坑我踩过一次大的。当时收到表空间告警,评估下来需要TRUNCATE AUD$表,结果TRUNCATE一执行就卡住不动了。查了半天,发现某监控工具每隔5分钟会查一次AUD$表做审计统计,查询会话一直开着,导致TRUNCATE拿不到锁。
排查办法:
-- 查看哪些会话在用AUD$相关对象 SELECT S.SID, S.SERIAL#, S.STATUS, S.USERNAME, S.MACHINE, S.PROGRAM FROM V$SESSION S WHERE S.SQL_ADDRESS IN ( SELECT CURRENT_OBJ FROM V$ACCESS WHERE OWNER = 'SYS' AND OBJECT_NAME = 'AUD$' );找到占用会话后,跟业务侧确认是否可以杀掉。如果不知道该会话是干嘛的,可以先SELECT * FROM V$SESSION_LONGOPS看看有没有在跑大查询,谨防误杀核心业务的会话。
提示:如果TRUNCATE一直卡住,不要反复去执行新的TRUNCATE。后面的语句都会排队等锁,只会把数据库搞得更乱。先杀锁,再重新评估。
4.2 DELETE太猛,UNDO撑爆了
分批DELETE的时候,我见过有人一上来就写DELETE FROM AUD$ WHERE ...;不带ROWNUM限制,一条语句删几千万行。结果UNDO表空间瞬间打满,数据库直接挂起。
这个问题的根源是对UNDO机制的理解不透:DELETE每删一行,原始数据的镜像就要写进UNDO,删的行越多,UNDO占用越大。就算你COMMIT了,UNDO里的数据在满足保留时间之前也不会立即释放。
保险的做法:
-- 删除前先查UNDO表空间使用率 SELECT TABLESPACE_NAME, SUM(BYTES)/1024/1024 AS USED_MB FROM DBA_UNDO_EXTENTS WHERE STATUS IN ('ACTIVE','UNEXPIRED') GROUP BY TABLESPACE_NAME;如果UNDO使用率已经超过70%,我一般会把每批删除量从5万降到1万甚至5000。慢一点没关系,稳定第一。生产环境的审计清理,宁可跑一晚上,也不要赌一条大SQL能秒完。
4.3 清完空间没释放,表空间还是满的
很多人清完AUD$表,发现表空间的“剩余空间”跟清理前差不多,于是怀疑自己操作无效。其实这很可能是高水位线问题。
DELETE语句不会降低表的高水位线,即使表里一行数据都没有了,段空间依然被之前存储过的数据占用着。所以DELETE之后必须做空间收缩:
-- 移动表并降低高水位线 ALTER TABLE SYS.AUD$ MOVE; -- 重建表的索引(MOVE之后索引会失效) ALTER INDEX SYS.AUD$ PK_REBUILD;MOVE操作会重建表并释放段空间,但这个操作本身是DDL,执行期间表不可用。所以你必须在业务低谷时段操作,否则会中断正在执行的审计写入。
这里有一个经验之谈:TRUNCATE天然会重置高水位线,所以如果你确认数据不需要保留,TRUNCATE比“DELETE+MOVE”这套组合高效得多。麻烦的是保留数据的需求,就必须靠MOVE来释放空间。
4.4 清理之后没过多久又满了
这种情况比“清不干净”更让人头疼,说明你的清理只是治标没治本。审计日志增长的速度如果大于你清理的速度,那再大的表空间都会变成无底洞。
排查重点放在两个地方:审计策略是不是开得太宽了、归档日志里面有多少策略是无效的。我遇到过一个场景,开发为了排查一个偶发报错,给某核心表开了一条FGA策略,记录所有UPDATE语句的绑定变量。排查结束后FGA策略没删,每秒钟几百条UPDATE就往审计表里灌。这种如果不堵源头,你一天清一次都清不完。
所以我的经验是:审计清理工作,三分在“清”,七分在“防”。
5. 从救火到防火:审计日志的长期治理方案
5.1 审计参数调优,在源头控增量
这一步值得你花点时间认真调一下。审计不是越全越好,而是越准越好。审计记录的价值是“事后可追溯”,不是为了给表空间添堵。
几个实用的参数调整思路:
-- 调整审计日志写入模式(需要重启实例,谨慎操作) -- DB_EXTENDED会记录SQL文本,空间消耗远大于DB ALTER SYSTEM SET AUDIT_TRAIL=DB SCOPE=SPFILE; -- 关闭不必要的FGA策略或缩小策略范围 BEGIN DBMS_FGA.DROP_POLICY( object_schema => 'APP_USER', object_name => 'T_ORDER', policy_name => 'T_ORDER_AUDIT' ); END; /经过评估,如果业务只需要知道谁在什么时候登录、执行了什么DDL,那就没必要审计所有DML。把审计策略从“全记录”改成“关键操作记录”,日志量能下降一个数量级。
5.2 定期自动清理与窗口任务
长期维护不能靠人工每月手点一次,一定要脚本化、自动化。我的思路是写一个存储过程,每个月自动执行一次审计日志清理,只保留最近N个月的审计记录。
下面是我常用的清理脚本模板(简化后),你可以根据自己的情况定制:
CREATE OR REPLACE PROCEDURE PRC_CLEAN_AUDIT_LOG AS V_KEEP_MONTHS NUMBER := 3; -- 保留近3个月 V_CUTOFF_DATE TIMESTAMP; V_COUNT NUMBER := 1; BEGIN -- 计算保留截止时间 V_CUTOFF_DATE := SYSTIMESTAMP - INTERVAL '3' MONTH; -- 分批删除过期的审计记录 WHILE V_COUNT > 0 LOOP DELETE FROM SYS.AUD$ WHERE NTIMESTAMP# < V_CUTOFF_DATE AND ROWNUM <= 20000; V_COUNT := SQL%ROWCOUNT; COMMIT; END LOOP; -- 收缩AUD$表空间 EXECUTE IMMEDIATE 'ALTER TABLE SYS.AUD$ MOVE'; -- 重建索引 EXECUTE IMMEDIATE 'ALTER INDEX SYS.AUD$_PK REBUILD'; END; /把这个存储过程挂到DBMS_SCHEDULER的 monthly job 里,每个月固定时间执行一次。要注意的坑是:JOB执行时间和业务高峰要错开,最好选在凌晨三四点这种业务空窗期;同时JOB执行期间要监控UNDO表空间和归档空间,防止意外。
5.3 分区表方案,一劳永逸
如果咱们退一步想,最优雅的审计日志管理方案其实是把AUD$表改成按月分区的分区表。分区表天然支持按分区TRUNCATE/DROP的快捷操作,清理某个月的数据就是一句话的事,既不用大DELETE,也不用重建高水位线。
不过,AUD$表是Oracle系统表,想改成分区表的动作不简单,需要依赖DBMS_AUDIT_MGMT包来做分区管理。12c以上版本的统一审计(Unified Auditing)已经默认把审计记录写到SYSAUX表空间而不是SYSTEM了,本身就缓解了SYSTEM表空间的压力。
Oracle统一审计里有一整套清理命令:
-- 创建审计清理任务(12c+统一审计模式) BEGIN DBMS_AUDIT_MGMT.INIT_CLEANUP( AUDIT_TRAIL_TYPE => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED, DEFAULT_CLEANUP_INTERVAL => 24 ); END; / -- 设置审计记录保留时间 BEGIN DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP( AUDIT_TRAIL_TYPE => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED, LAST_ARCHIVE_TIME => SYSTIMESTAMP - INTERVAL '3' MONTH ); END; / -- 执行清理 BEGIN DBMS_AUDIT_MGMT.CLEAN_AUDIT_TRAIL( AUDIT_TRAIL_TYPE => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED ); END; /这套命令的好处是全自动、按保留时间管理、不需要手动DELETE。如果你们库是12c以上版本,强烈建议直接用统一审计的清理机制,省心太多了。
5.4 巡检制度与保留策略
最后给一套我自己的巡检清单,照着做基本不会出大乱子:
- 每周检查一次SYSTEM和SYSAUX表空间使用率,超过80%就要留神。
- 每月检查一次AUD$和FGA_LOG$的空间占用趋势,连续两个月增长超过20%就要查审计策略。
- 审计策略每季度复盘一次,下线了很久不用的策略,别舍不得删。
- 明确审计数据的保留周期,这个周期内能追溯,过了周期尽快清理。
- 所有清理操作记录到维护文档,日期、操作人、清理量、耗时都记下来,方便回溯。
这套巡检制度不需要额外开发什么复杂平台,一张Excel表就能管起来。但坚持执行下来,审计日志就再也不是悬在头上的定时炸弹了。
我个人在实际操作中最深的体会是:处理审计日志膨胀,最忌讳的就是“急”。越着急清理,越容易忽略锁会话、UNDO空间这些前置条件,结果把一个小问题弄成大故障,这种事每个月都在不同的生产库里上演。按部就班地查空间、查策略、选方案、挑窗口、盯监控,这套流程走一遍,30分钟到一个小时内就处理完了,而且干干净净不留尾巴。你如果能把前面那三查三看和几种清理方案吃透,以后遇到同款问题,基本都能做到心里有底,手上有活。