DBA:在生产环境中创建监控表DML的触发器 |
发布时间:2008.07.11 04:47 来源:赛迪网 作者:Andy |
【赛迪网-IT技术报道】在生产环境中,总是可能出现这样的情况:某张或者某些表的数据被莫名其妙的修改了,但是很难定位出是哪个用户、哪个过程修改的。这是一个很让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来定位。(责任编辑:卢兆林) |