在生产环境中,总是可能出现这样的情况:某张或者某些表的数据被莫名其妙的修改了,但是很难定位出是哪个用户、哪个过程修改的。这是一个很让DBA头痛的事情(往往DBA对于整个代码逻辑并不是非常了解)。要定位出“问题”语句,有几种方法可以选择:log miner;细节粒度审计;触发器。Log miner要求要有归档日志(这个并非所有系统都可以做),而且需要有相当的磁盘空间,好处就是可以离线做;细节粒度升级能够根据条件记录下表的DML操作(9i及之前只能记录SELECT语句),比较复杂的FGA需要较高权限的用户来实现;触发器比较灵活,能够按照比较复杂的条件来记录需要的信息。下面介绍触发器如何实现。
要建立这样的触发器,需要利用到几张系统视图:v$session, v$sql, v$cursor,(10g, 9.2.0.1中可以,9.2.0.5, 9.2.0.之前存在bug)
SQL> connect "/ as sysdba"
grant select on SYS.V_$SQL to demo;
grant select on SYS.V_$SQL_BIND_DATA to demo;
grant select on SYS.V_$SQL_CURSOR to demo;
grant select on SYS.V_$SESSION to demo;
grant create trigger to demo;
CREATE TABLE trig_sql(lt DATE, sid NUMBER, SERIAL# NUMBER,
USERNAME VARCHAR2(30), OSUSER VARCHAR2(64),
MACHINE VARCHAR2(32), TERMINAL VARCHAR2(16),
PROGRAM VARCHAR2(64), sqlText VARCHAR2(2000),
status VARCHAR2(30));
方法1:
create or replace trigger ttt_trig
after insert or update on pga_ttt
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
begin
INSERT INTO trig_sql
select sysdate,s.SID, s.SERIAL#, s.USERNAME, s.OSUSER,
s.MACHINE, s.TERMINAL, s.PROGRAM, q.sql_text line,
'NONE'
from v$sql q, v$session s
where s.audsid=(select userenv('SESSIONID') from dual)
and s.prev_sql_addr=q.address
AND s.PREV_HASH_VALUE = q.hash_value;
COMMIT;
end;
方法2:
create or replace trigger ttt_trig
after insert or update on pga_ttt
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
begin
for cr in (select s.SID, s.SERIAL#, s.USERNAME, s.OSUSER,
s.MACHINE, s.TERMINAL, s.PROGRAM,
q.sql_text line, c.status stat
from v$sql q, v$sql_cursor c, v$session s
where s.audsid=(select userenv('SESSIONID') from dual)
and s.prev_sql_addr=q.address
AND c.STATUS = 'CURFETCH')
loop
INSERT INTO trig_sql VALUES(SYSDATE, cr.sid, cr.SERIAL#,
cr.USERNAME, cr.OSUSER, cr.MACHINE,
cr.TERMINAL, cr.PROGRAM, cr.line,
cr.stat);
end loop;
COMMIT;
end;
第一种方法是通过前一SQL的地址(pre_sql_addr)和HASH(prev_hash_value)值来定位出发trigger的语句的,不能用sql_address和hash_value来定位,否则获取到是触发器里面向日志表插入记录数据的语句本身了。
第二个方法是通过通过地址加游标的方法,按照视图各个字段的解释,应该是可以通过v$sql_cursor.parent_handle来定位的。但是通过测试发现,只有当前一条语句和查找前一条语句的语句在一个PLSQL块中的时候才有效,
SQL> set serveroutput on
SQL> declare
2 v_date date;
3 v_sql varchar2(2000);
4 begin
5 select sysdate into v_date from dual;
6
7 select q.sql_text into v_sql
8 from v$sql q, v$sql_cursor c, v$session s
9 where s.audsid=(select userenv('SESSIONID') from dual)
10 and s.prev_sql_addr=q.address and q.address=c.parent_handle;
11
12 dbms_output.put_line(v_sql);
13 end;
14 /
SELECT SYSDATE FROM DUAL
PL/SQL procedure successfully completed.
因为触发器本身是一个PLSQL块,所以总是无法获得正确语句,最后只有通过cursor的状态来获取。下面简单了解一下CURSOR各个状态的含义:
·CURNULL:游标已经存在,但没有任何SQL语句在使用它(即cache在每个session内存中的游标)
·CURSYNTAX:解析SQL语句过程的一个游标状态,说明调用游标的SQL语句语法正确,但是没有解析完成。
·CURPARSE:调用游标的语句解析完毕
·CURBOUND:游标使用了帮定变量,并定义好了帮定变量
·CURFETCH:游标执行完毕,并fetch了数据
·CURROW:游标正指向某一行
·ERROR:游标错误,一般是有BUG了。
当一条INSERT或者UPDATE语句执行以后才会触发触发器,所以这时候的游标状态是CURFETCH,我们这就通过状态为CURFETCH来定位。