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;