oracle中,經過觸發器,記錄每一個語句影響總行數

需求產生:

       業務系統中,有一步「抽數」流程,就是把一些數據從其它服務器同步到本庫的目標表。這個過程有可能 多人同時抽數,互相影響。有測試人員反應,原來抽過的數,偶爾就平白無故的找不到了,有時又會出來重複行。這個問題產生確定是抽數邏輯問題以及並行的問題了!但他們提了一個簡單的需求:想知道何時數據被刪除了,何時插入了,我須要監控「表的每一次變動」!sql

技術選擇:

     第一就想到觸發器,這樣能在不涉及業務系統的代碼狀況下,實現監控。觸發器分爲「語句級觸發器」和「行級觸發器」。語句級是每個語句執行先後觸發一次操做,若是我在每個SQL語句執行後,把表名,時間,影響行寫到記錄表裏就好了。數據庫

     但問題來了,在語句觸發器中,沒法獲得該語句的行數,sql%rowcount  在觸發器裏報錯。只能用行級觸發器去統計行數!數組

代碼結構:

整個監控數據行的功能包含: 一個日誌表,包,序列。服務器

日誌表:記錄目標表名,SQL執行開始、結束時間,影響行數,監控數據行上的某些列信息。oop

包:主要是3個存儲過程,測試

  1. 語句開始存儲過程:用關聯數組來記錄目標表名和開始時間,把其它值清0.
  2. 行操做存儲過程:把關聯數組目標表所對應的記錄數加1。
  3. 語句結束存儲過程:把關聯數組目標表中統計的信息寫到日誌表。

序列: 用於生成日誌表的主鍵spa

代碼:

日誌表和序列:日誌

create table T_CSLOG
(
  n_id     NUMBER not null,
  tblname  VARCHAR2(30) not null,
  sj1      DATE,
  sj2      DATE,
  i_hs     NUMBER,
  u_hs     NUMBER,
  d_hs     NUMBER,
  portcode CLOB,
  startrq  DATE,
  endrq    DATE,
  bz       VARCHAR2(100),
  n        NUMBER
)
create index IDX_T_CSLOG1 on T_CSLOG (TBLNAME, SJ1, SJ2)
alter table T_CSLOG  add constraint PRIKEY_T_CSLOG primary key (N_ID)

   
create sequence SEQ_T_CSLOG
minvalue 1
maxvalue 99999999999
start with 1
increment by 1
cache 20
cycle;

包代碼:code

--包頭
create or replace package pck_cslog is
  --聲明一個關聯數組類型,它就是日誌表的關聯數組
  type cslog_type is table of t_cslog%rowtype index by t_cslog.tblname%type;
  --聲明這個關聯數組的變量。
  cslog_tbl cslog_type;
  --語句開始。  
  procedure onbegin_cs(v_tblname t_cslog.tblname%type, v_type varchar2);
  --行操做
  procedure oneachrow_cs(v_tblname t_cslog.tblname%type,
                         v_type    varchar2,
                         v_code    varchar2 := '',
                         v_rq      date := '');
  --語句結束,寫到日誌表中。
  procedure onend_cs(v_tblname t_cslog.tblname%type, v_type varchar2);
end pck_cslog;

--包體
create or replace package body pck_cslog is
  --私有方法,把關聯數組中的一條記錄寫入庫裏
  procedure write_cslog(v_tblname t_cslog.tblname%type) is
  begin
    if cslog_tbl.exists(v_tblname) then
      insert into t_cslog values cslog_tbl (v_tblname);
    end if;
  end;
  --私有方法,清除關聯數組中的一條記錄
  procedure clear_cslog(v_tblname t_cslog.tblname%type) is
  begin
    if cslog_tbl.exists(v_tblname) then
      cslog_tbl.delete(v_tblname);
    end if;
  end;
  --某個SQL語句執行開始。 v_type:語句類型,insert時爲 i, update時爲u ,delete時爲 d
  procedure onbegin_cs(v_tblname t_cslog.tblname%type, v_type varchar2) is
  begin
     --若是關聯數組中不存在,初始賦值。 不然表示,同時有insert,delete語句對目標表操做。
    if not cslog_tbl.exists(v_tblname) then
      cslog_tbl(v_tblname).n_id := seq_t_cslog.nextval;
      cslog_tbl(v_tblname).tblname := v_tblname;
      cslog_tbl(v_tblname).sj1 := sysdate;
      cslog_tbl(v_tblname).sj2 := null;
      cslog_tbl(v_tblname).i_hs := 0;
      cslog_tbl(v_tblname).u_hs := 0;
      cslog_tbl(v_tblname).d_hs := 0;
      cslog_tbl(v_tblname).portcode := ' '; --初始給一個空格
      cslog_tbl(v_tblname).startrq := to_date('9999', 'yyyy');
      cslog_tbl(v_tblname).endrq := to_date('1900', 'yyyy');
      cslog_tbl(v_tblname).n := 0;
    end if;
    cslog_tbl(v_tblname).bz := cslog_tbl(v_tblname).bz || v_type || ',';
    ----第一個語句進入,顯示1,若是之後並行,則該值遞增。
    cslog_tbl(v_tblname).n := cslog_tbl(v_tblname).n + 1;  
  end;
  --每行操做。
  procedure oneachrow_cs(v_tblname t_cslog.tblname%type,
                         v_type    varchar2,
                         v_code    varchar2 := '',
                         v_rq      date := '') is
  begin
    if cslog_tbl.exists(v_tblname) then
      --行數,代碼,起、止時間
      if v_type = 'i' then
        cslog_tbl(v_tblname).i_hs := cslog_tbl(v_tblname).i_hs + 1;
      elsif v_type = 'u' then
        cslog_tbl(v_tblname).u_hs := cslog_tbl(v_tblname).u_hs + 1;
      elsif v_type = 'd' then
        cslog_tbl(v_tblname).d_hs := cslog_tbl(v_tblname).d_hs + 1;
      end if;
      
      if v_code is not null and
         instr(cslog_tbl(v_tblname).portcode, v_code) = 0 then
        cslog_tbl(v_tblname).portcode := cslog_tbl(v_tblname).portcode || ',' || v_code;
      end if;
    
      if v_rq is not null then
        if v_rq > cslog_tbl(v_tblname).endrq then
          cslog_tbl(v_tblname).endrq := v_rq;
        end if;
        if v_rq < cslog_tbl(v_tblname).startrq then
          cslog_tbl(v_tblname).startrq := v_rq;
        end if;
      end if;
    end if;
  end;
  --語句結束。 
  procedure onend_cs(v_tblname t_cslog.tblname%type, v_type varchar2) is
  begin
    if cslog_tbl.exists(v_tblname) then
      cslog_tbl(v_tblname).bz := cslog_tbl(v_tblname)
                                 .bz || '-' || v_type || ',';
      --語句退出,將並行標誌位減一。 當它爲0時,就能夠寫表了
      cslog_tbl(v_tblname).n := cslog_tbl(v_tblname).n - 1;
      if cslog_tbl(v_tblname).n = 0 then
        cslog_tbl(v_tblname).sj2 := sysdate;
        write_cslog(v_tblname);
        clear_cslog(v_tblname);
      end if;
    end if;
  end;

begin
  null;
end pck_cslog;

綁定觸發器:

有了以上代碼後,想要監控的一個目標表,只須要給它添加三個觸發器,調用包裏對應的存儲過程便可。  假定我要監控  T_A 的表:rem

     

須要給T_A添加三個觸發器:

--語句開始前
create or replace trigger tri_onb_t_a
  before insert or delete or update on t_a
declare
  v_type varchar2(1);
begin
  if inserting then    v_type := 'i';  elsif updating then    v_type := 'u';  elsif deleting then    v_type := 'd';  end if;
  pck_cslog.onbegin_cs('t_a', v_type);
end;

--語句結束後
create or replace trigger tri_one_t_a
  after insert or delete or update on t_a
declare
  v_type varchar2(1);
begin
  if inserting then    v_type := 'i';  elsif updating then    v_type := 'u';  elsif deleting then    v_type := 'd';  end if;
  pck_cslog.onend_cs('t_a', v_type);
end;

--行級觸發器
create or replace trigger tri_onr_t_a
  after insert or delete or update on t_a
  for each row
declare
  v_type varchar2(1);
begin
  if inserting then    v_type := 'i';  elsif updating then    v_type := 'u';  elsif deleting then    v_type := 'd';  end if;
  if v_type = 'i' or v_type = 'u' then
    pck_cslog.oneachrow_cs('t_a', v_type, :new.name);  --此處是把監控的行的某一列的值傳入包體,這樣最後會記錄到日誌表
  elsif v_type = 'd' then
    pck_cslog.oneachrow_cs('t_a', v_type, :old.name);
  end if;
end;

在行級觸發器時裏,調用了:pck_cslog.oneachrow_cs方法。 這個方法接受四個參數:

procedure oneachrow_cs(v_tblname t_cslog.tblname%type,  --表名
                         v_type    varchar2,              --DML類型,i,u,d
                         v_code    varchar2 := '',        --監控數據行的某個主鍵列
                         v_rq      date := '');           --監控數據行的時間範圍

後兩個參數有什麼用呢?

若是你不關心插入或刪除行的信息的話,那你直接調用pck_cslog.oneachrow_cs('t_a', v_type);就能夠了。

若是你監控這些數據行的一些列值,像我這裏的業務系統,多數表都是一個主鍵+日期+業務數據這種結構。這裏假設要監控T_TRADE表,監控影響的code和rq列,那麼調用時按下面方法寫,就能監控一條SQL調用影響的全部主鍵及日期範圍了:

pck_cslog.oneachrow_cs('T_TRADE', v_type, :new.code,  :new.rq);

測試成果:

觸發器建好了,能夠測試插入刪除了。先插入100行,再隨便刪除一些行。

declare
  i number;
begin
  for i in 1 .. 100 loop
    insert into t_a values (i, i || 'shenjunjian');
  end loop;
  commit;
  
  delete from t_a   where id > 79;
  delete from t_a   where id < 40;
  commit;
end;

clob列,還能夠顯示監控刪除的行:

並行時,在bz列中,可能會有相似信息:

i,i,-i,-i  ,這表示同一時間有2個語句在插入目標表。

i,d,-d,-i  表示在插入時,有一個刪除語句也在執行。

當平臺多人在用時,避免不了有同時操做同一張表的狀況,經過這個列的值,能夠觀察到數據庫的執行狀況!

相關文章
相關標籤/搜索