Oracle PL/SQL進階編程(第十三彈:系統事件觸發器)

DML觸發器和替代觸發器都是在DML事件上觸發的 ,而系統觸發器是在DDL事件和數據庫服務器事件時觸發的。

定義系統觸發器

語法如下:

CREATE [OR REPLACE] TRIGGER trigger_name
{BEFORE | AFTER} {DDL event |DATABASE event}
ON {DATABASE | SCHEMA}
[WHEN [...]]
plsql_block

DDL event包括ALTER、ANALYZE、ASSOCIATE STATISTICS、AUDIT、COMMENT、CREATE、DROP、GRANT、RENAME、REVOKE、TRUNCATE等。
DATABASE event是數據庫級的系統事件,對每一個觸發的事件,Oracle會打開一個匿名事務,觸發觸發器,提交任何獨立的事務,這些事件有SERVERERROR、LOGON、LOGOFF、STARTUP、SHUTDOWN、SUSPEND。

在創建數據庫級的觸發器時,必須具有ADMINISTER DATABASE TRIGGER的系統權限。

代碼如下:

CREATE TABLE created_log
(
    obj_owner VARCHAR2(30),   --所有者
    obj_name  VARCHAR2(30),   --對象名稱
    obj_type  VARCHAR2(20),   --對象類型
    obj_user VARCHAR2(30),    --創建用戶
    created_date DATE     --創建日期
)

CREATE OR REPLACE TRIGGER t_created_log
   AFTER CREATE ON scott.SCHEMA                  --在soctt方案下創建對象後觸發
BEGIN
   --插入日誌記錄
   INSERT INTO scott.created_log(obj_owner, obj_name,
                obj_type, obj_user, created_date
               )
        VALUES (SYS.dictionary_obj_owner, SYS.dictionary_obj_name,
                SYS.dictionary_obj_type, SYS.login_user, SYSDATE
               );
END;

觸發器事件列表

DDL觸發器事件列表如下:

事件 觸發時機 描述
CRAETE BEFORE/AFTER 在創建一個方案對象之前或之後觸發,比如創建表、索引等對象
DROP BEFORE/AFTER 在刪除一個方案對象之前或之後觸發
ALTER BEFORE/AFTER 在修改一個方案對象之前或之後觸發
ANALYZE BEFORE/AFTER 在使用ANALYZE分析數據庫對象之前或之後觸發
ASSOCIATE STATISTICS BEFORE/AFTER 統計相關的數據庫對象之前或之後觸發
AUDIT BEFORE/AFTER 在使用AUDIT開啓審計功能之前或之後觸發
COMMENT BEFORE/AFTER 在對一個數據庫對象應用註釋之前或之後觸發
DISASSOCIATE STATISTICS BEFORE/AFTER 取消對一個數據庫對象的統計之前或之後觸發
GRANT BEFORE/AFTER 在使用GRANT分配權限之前或之後觸發
NOAUDIT BEFORE/AFTER 在使用NOAUDIT語句關閉審計功能之前或之後觸發
RENAME BEFORE/AFTER 在使用RENAME對一個數據庫對象進行重命名之前或之後觸發
REVOKE BEFORE/AFTER 在使用REVOKE語句取消權限之前或之後觸發
TRUNCATE BEFORE/AFTER 在使用TRUNCATE清除一個表的內容之前或之後觸發
DDL BEFORE/AFTER DDL指定在本表格中列出的任何事件執行之前或之後觸發

數據庫系統觸發器事件列表如下:

事件 觸發時機 描述
STARTUP AFTER 當數據庫實例啓動後觸發
SHUTDOWN BEFORE 當數據庫實例關閉前觸發,如果數據庫是異常關閉,那麼這個時間可能不會觸發
SERVERERROR AFTER 只要發生錯誤就會被觸發
LOGON AFTER 當一個用戶成功連接到該數據庫後觸發
LOGOFF BEFORE 當用戶註銷前觸發

來看一段代碼:

--以DBA身份登錄,創建下面的登錄記錄表
CREATE TABLE log_db_table
(
   username VARCHAR2(20),
   logon_time DATE,
   logoff_time DATE,
   address VARCHAR2(20) 
);

--以DBA身份登錄,創建DATABASE級別的LOGON事件觸發器
CREATE OR REPLACE TRIGGER t_db_logon
AFTER LOGON ON DATABASE
BEGIN
  INSERT INTO log_db_table(username,logon_time,address)
              VALUES(ora_login_user,SYSDATE,ora_client_ip_address);
END;

觸發器屬性列表

Oracle在DBMS_STANDARD包中提供了一些功能性的函數,以便在開發系統級別的觸發器時可以提供一些系統級別的信息。

屬性函數 描述
ora_client_ip_address 返回客戶端的IP地址
ora_database_name 返回當前數據庫的名稱
ora_des_encrypted_password 返回DES加密後的用戶口令
ora_dict_obj_name 返回DDL操作所對應的數據庫對象名
ora_dict_obj_name_list(name_list OUT ora_name_list_t) 返回在事件中被修改的對象名列表
ora_dict_obj_owner 返回DDL操作所對應的對象的所有者名稱
ora_dict_obj_owner_list(owner_list OUT ora_name_list_t) 返回在事件中被修改的對象的所有者列表
ora_dict_obj_type 返回DDL操作所對應的數據庫對象的類型
ora_grantee(user_list OUT ora_name_list_t) 返回授權事件的授權者
ora_instance_num 返回例程號
ora_is_alter_column(column_name IN VARCHAR2) 檢測特定列是否被修改
ora_is_creating_nested_table 用於檢測是否正在建立嵌套表
ora_is_servererroe(error_number) 用於檢測是否返回了特定Oracle錯誤
ora_login_user 用於範湖登錄用戶名
ora_sysevent 用戶返回觸發觸發器的系統事件名
ora_is_drop_column 如果指定的columnname正在被移除,則返回True,否則返回False
ora_is_alter_column 如果指定的columnname已經被修改,則返回True,否則返回False

這裏的ora_name_list_t是定義在DBMS_STANDARD包中的一個嵌套表類型,定義如下:
TYPE ora_name_list_t IS TABLE OF VARCHAR2(64);

屬性函數使用示例

如下代碼演示了在數據庫啓動和關閉時,使用ora_sysevent來獲取系統事件的名稱:

--以DBA身份進入系統,創建臨時表
CREATE TABLE event_table(
   sys_event VARCHAR2(30),
   event_time DATE
);
--在DBA級別創建如下的2個觸發器
CREATE OR REPLACE TRIGGER t_startup
AFTER STARTUP ON DATABASE       --STARTUP只能是AFTER
BEGIN
   INSERT INTO event_table VALUES(ora_sysevent,SYSDATE);
END;
/
CREATE OR REPLACE TRIGGER t_startup
BEFORE SHUTDOWN ON DATABASE       --SHUTDOWN只能是BEFORE
BEGIN
   INSERT INTO event_table VALUES(ora_sysevent,SYSDATE);
END;
/

下面的代碼演示瞭如何使用ora_is_alter_column來防止用戶對emp表的empno字段進行修改,用ora_is_drop_column來防止emp表的empno字段被刪除:

CREATE OR REPLACE TRIGGER preserve_app_cols
   AFTER ALTER ON SCHEMA
DECLARE
   --獲取一個表中所有列的遊標
   CURSOR curs_get_columns (cp_owner VARCHAR2, cp_table VARCHAR2)
   IS
      SELECT column_name
        FROM all_tab_columns
       WHERE owner = cp_owner AND table_name = cp_table;
BEGIN
   -- 如果正使用的是ALTER TABLE語句修改表
   IF ora_dict_obj_type = 'TABLE'
   THEN
      -- 循環表中的每一列
      FOR v_column_rec IN curs_get_columns (
                             ora_dict_obj_owner,
                             ora_dict_obj_name
                          )
      LOOP
         --判斷當前的列名正在被修改
         IF ORA_IS_ALTER_COLUMN (v_column_rec.column_name) THEN
            IF v_column_rec.column_name='EMPNO' THEN
               RAISE_APPLICATION_ERROR (-20003, '不能對empno字段進行修改');
            END IF; 
         END IF; 
         IF ORA_IS_DROP_COLUMN('EMPNO') THEN
            RAISE_APPLICATION_ERROR (-20004, '不能對empno字段進行刪除');
         END IF; 
      END LOOP;
   END IF;
END;

定義SERVERERROR觸發器

SERVERERROR事件可以用來跟蹤數據庫中發生的錯誤,錯誤代碼可以通過SERVER_ERROR屬性函數在觸發器內部得到,可以通過該函數確定堆棧中的錯誤代碼,可以使用DBMS_UTILITY.FORMAT_ERROR_STACK獲取錯誤信息。
使用AFTER SERVERERROR時必須要了解如下的錯誤是否會被觸發:
- ORA-00600:Oracle內部錯誤
- ORA-01034:Oracle不可用
- ORA-01403:沒有找到數據
- ORA-01422:提取操作返回大於請求的行數
- ORA-01423:在一個提取操作中檢測到額外的行
- ORA-04030:在分配字節時內存不夠

AFTER SERVERERROR觸發器並沒有提供方法來調整出現的錯誤,僅能包含錯誤的相關的信息,管理員可以通過使用這些觸發器來構建強大的日誌機制。

如下代碼演示瞭如何通過創建AFTER SERVERERROR觸發器來記錄數據庫服務器發生的各種錯誤:

--錯誤日誌記錄表
CREATE TABLE servererror_log(
   error_time DATE,
   username  VARCHAR2(30),
   instance NUMBER,
   db_name VARCHAR2(50),
   error_stack VARCHAR2(2000)
);
--創建錯誤觸發器,在出現數據庫錯誤時觸發。
CREATE OR REPLACE TRIGGER t_logerrors
   AFTER SERVERERROR ON DATABASE
BEGIN
   INSERT INTO servererror_log
        VALUES (SYSDATE, login_user, instance_num, database_name,
                DBMS_UTILITY.format_error_stack);
END;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章