在这里插入图片描述

个人主页:Guiat
归属专栏:Oracle

在这里插入图片描述

文章目录

  • 1. 触发器基础概述
    • 1.1 触发器的概念与特点
    • 1.2 触发器的分类
    • 1.3 触发器的执行顺序
  • 2. DML触发器
    • 2.1 基础DML触发器
      • 2.1.1 INSERT触发器
      • 2.1.2 UPDATE触发器
      • 2.1.3 DELETE触发器
    • 2.2 高级DML触发器
      • 2.2.1 复合触发器
      • 2.2.2 条件触发器
  • 3. INSTEAD OF 触发器
    • 3.1 视图上的INSTEAD OF触发器
      • 3.1.1 复杂视图的DML操作
  • 4. DDL触发器
    • 4.1 系统级DDL触发器
      • 4.1.1 数据库对象变更监控
      • 4.1.2 防止意外删除的保护触发器
  • 5. 系统事件触发器
    • 5.1 登录和注销触发器
      • 5.1.1 用户会话监控

正文

1. 触发器基础概述

触发器是Oracle数据库中的一种特殊存储过程,当特定的数据库事件发生时自动执行。它是实现业务规则、数据完整性约束和审计功能的重要工具。

1.1 触发器的概念与特点

Oracle触发器
自动执行
事件驱动
透明性
不可直接调用
无需手动调用
响应特定事件
自动激活
DML操作触发
DDL操作触发
系统事件触发
对应用透明
自动数据处理
隐式执行
不能直接CALL
通过事件激活
系统自动管理

1.2 触发器的分类

Oracle触发器分类
按触发事件分类
按触发时机分类
按触发级别分类
按触发次数分类
DML触发器
DDL触发器
系统事件触发器
用户事件触发器
BEFORE触发器
AFTER触发器
INSTEAD OF触发器
行级触发器
语句级触发器
FOR EACH ROW
FOR EACH STATEMENT

1.3 触发器的执行顺序

DML语句执行
BEFORE语句级触发器
BEFORE行级触发器
实际DML操作
AFTER行级触发器
AFTER语句级触发器
提交/回滚

2. DML触发器

2.1 基础DML触发器

2.1.1 INSERT触发器

-- 创建员工表和相关表用于演示
CREATE TABLE employees_demo AS SELECT * FROM employees WHERE 1=0;
ALTER TABLE employees_demo ADD CONSTRAINT pk_emp_demo PRIMARY KEY (employee_id);-- 创建员工审计表
CREATE TABLE employee_audit (audit_id NUMBER PRIMARY KEY,employee_id NUMBER,operation_type VARCHAR2(10),old_values VARCHAR2(4000),new_values VARCHAR2(4000),changed_by VARCHAR2(30),change_date DATE,session_id NUMBER
);CREATE SEQUENCE employee_audit_seq START WITH 1 INCREMENT BY 1;-- 创建INSERT触发器
CREATE OR REPLACE TRIGGER trg_employee_insert_auditAFTER INSERT ON employees_demoFOR EACH ROW
BEGIN-- 记录新员工插入的审计信息INSERT INTO employee_audit (audit_id,employee_id,operation_type,new_values,changed_by,change_date,session_id) VALUES (employee_audit_seq.NEXTVAL,:NEW.employee_id,'INSERT','ID: ' || :NEW.employee_id || ', Name: ' || :NEW.first_name || ' ' || :NEW.last_name ||', Email: ' || :NEW.email ||', Salary: ' || :NEW.salary ||', Hire Date: ' || TO_CHAR(:NEW.hire_date, 'YYYY-MM-DD'),USER,SYSDATE,SYS_CONTEXT('USERENV', 'SESSIONID'));-- 输出调试信息DBMS_OUTPUT.PUT_LINE('触发器执行: 新员工 ' || :NEW.first_name || ' ' || :NEW.last_name || ' 已插入');
END;
/-- 测试INSERT触发器
SET SERVEROUTPUT ONINSERT INTO employees_demo (employee_id, first_name, last_name, email, hire_date, job_id, salary
) VALUES (1001, 'John', 'Doe', 'john.doe@company.com', SYSDATE, 'IT_PROG', 6000
);INSERT INTO employees_demo (employee_id, first_name, last_name, email, hire_date, job_id, salary
) VALUES (1002, 'Jane', 'Smith', 'jane.smith@company.com', SYSDATE, 'SA_REP', 5500
);COMMIT;-- 查看审计记录
SELECT * FROM employee_audit ORDER BY change_date DESC;

2.1.2 UPDATE触发器

-- 创建UPDATE触发器
CREATE OR REPLACE TRIGGER trg_employee_update_auditAFTER UPDATE ON employees_demoFOR EACH ROW
DECLAREv_changes VARCHAR2(4000);
BEGIN-- 构建变更信息v_changes := '';IF :OLD.first_name != :NEW.first_name THENv_changes := v_changes || 'First Name: ' || :OLD.first_name || ' -> ' || :NEW.first_name || '; ';END IF;IF :OLD.last_name != :NEW.last_name THENv_changes := v_changes || 'Last Name: ' || :OLD.last_name || ' -> ' || :NEW.last_name || '; ';END IF;IF :OLD.email != :NEW.email THENv_changes := v_changes || 'Email: ' || :OLD.email || ' -> ' || :NEW.email || '; ';END IF;IF :OLD.salary != :NEW.salary THENv_changes := v_changes || 'Salary: ' || :OLD.salary || ' -> ' || :NEW.salary || '; ';END IF;IF :OLD.job_id != :NEW.job_id OR (:OLD.job_id IS NULL AND :NEW.job_id IS NOT NULL) OR (:OLD.job_id IS NOT NULL AND :NEW.job_id IS NULL) THENv_changes := v_changes || 'Job: ' || NVL(:OLD.job_id, 'NULL') || ' -> ' || NVL(:NEW.job_id, 'NULL') || '; ';END IF;-- 只有当有实际变更时才记录IF LENGTH(v_changes) > 0 THENINSERT INTO employee_audit (audit_id,employee_id,operation_type,old_values,new_values,changed_by,change_date,session_id) VALUES (employee_audit_seq.NEXTVAL,:NEW.employee_id,'UPDATE','Original: ID=' || :OLD.employee_id || ', Name=' || :OLD.first_name || ' ' || :OLD.last_name || ', Email=' || :OLD.email || ', Salary=' || :OLD.salary,'Updated: ' || RTRIM(v_changes, '; '),USER,SYSDATE,SYS_CONTEXT('USERENV', 'SESSIONID'));DBMS_OUTPUT.PUT_LINE('触发器执行: 员工 ' || :NEW.employee_id || ' 信息已更新');DBMS_OUTPUT.PUT_LINE('变更内容: ' || RTRIM(v_changes, '; '));END IF;
END;
/-- 测试UPDATE触发器
UPDATE employees_demo 
SET salary = 6500, email = 'john.doe.new@company.com'
WHERE employee_id = 1001;UPDATE employees_demo 
SET first_name = 'Janet'
WHERE employee_id = 1002;COMMIT;-- 查看更新审计记录
SELECT * FROM employee_audit WHERE operation_type = 'UPDATE' ORDER BY change_date DESC;

2.1.3 DELETE触发器

-- 创建DELETE触发器
CREATE OR REPLACE TRIGGER trg_employee_delete_auditBEFORE DELETE ON employees_demoFOR EACH ROW
BEGIN-- 记录删除前的员工信息INSERT INTO employee_audit (audit_id,employee_id,operation_type,old_values,changed_by,change_date,session_id) VALUES (employee_audit_seq.NEXTVAL,:OLD.employee_id,'DELETE','Deleted: ID=' || :OLD.employee_id || ', Name=' || :OLD.first_name || ' ' || :OLD.last_name ||', Email=' || :OLD.email ||', Salary=' || :OLD.salary ||', Job=' || :OLD.job_id,USER,SYSDATE,SYS_CONTEXT('USERENV', 'SESSIONID'));DBMS_OUTPUT.PUT_LINE('触发器执行: 员工 ' || :OLD.first_name || ' ' || :OLD.last_name || ' 即将被删除');
END;
/-- 测试DELETE触发器
DELETE FROM employees_demo WHERE employee_id = 1002;
COMMIT;-- 查看删除审计记录
SELECT * FROM employee_audit WHERE operation_type = 'DELETE' ORDER BY change_date DESC;

2.2 高级DML触发器

2.2.1 复合触发器

-- 创建复合触发器,处理工资历史记录
CREATE TABLE salary_history (history_id NUMBER PRIMARY KEY,employee_id NUMBER,old_salary NUMBER,new_salary NUMBER,change_date DATE,change_reason VARCHAR2(200),effective_date DATE
);CREATE SEQUENCE salary_history_seq START WITH 1 INCREMENT BY 1;-- 复合触发器
CREATE OR REPLACE TRIGGER trg_salary_managementFOR UPDATE OF salary ON employees_demoCOMPOUND TRIGGER-- 声明部分:定义包级变量TYPE emp_salary_rec IS RECORD (employee_id NUMBER,old_salary NUMBER,new_salary NUMBER);TYPE emp_salary_tab IS TABLE OF emp_salary_rec INDEX BY PLS_INTEGER;g_salary_changes emp_salary_tab;g_change_count PLS_INTEGER := 0;-- BEFORE STATEMENT: 语句执行前BEFORE STATEMENT ISBEGINDBMS_OUTPUT.PUT_LINE('=== 工资更新开始 ===');DBMS_OUTPUT.PUT_LINE('时间: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));g_change_count := 0;g_salary_changes.DELETE; -- 清空集合END BEFORE STATEMENT;-- BEFORE EACH ROW: 每行处理前BEFORE EACH ROW ISBEGIN-- 验证工资变更规则IF :NEW.salary <= 0 THENRAISE_APPLICATION_ERROR(-20001, '工资必须大于0');END IF;IF :NEW.salary > :OLD.salary * 2 THENRAISE_APPLICATION_ERROR(-20002, '工资增长不能超过100%');END IF;IF :NEW.salary < :OLD.salary * 0.5 THENRAISE_APPLICATION_ERROR(-20003, '工资减少不能超过50%');END IF;DBMS_OUTPUT.PUT_LINE('验证通过: 员工' || :NEW.employee_id || ' 工资从 $' || :OLD.salary || ' 变更为 $' || :NEW.salary);END BEFORE EACH ROW;-- AFTER EACH ROW: 每行处理后AFTER EACH ROW ISBEGIN-- 收集变更信息g_change_count := g_change_count + 1;g_salary_changes(g_change_count).employee_id := :NEW.employee_id;g_salary_changes(g_change_count).old_salary := :OLD.salary;g_salary_changes(g_change_count).new_salary := :NEW.salary;DBMS_OUTPUT.PUT_LINE('记录变更: 员工' || :NEW.employee_id || ' 工资变更已收集');END AFTER EACH ROW;-- AFTER STATEMENT: 语句执行后AFTER STATEMENT ISv_total_old_salary NUMBER := 0;v_total_new_salary NUMBER := 0;v_avg_increase_pct NUMBER;BEGIN-- 批量插入工资历史记录FOR i IN 1..g_change_count LOOPINSERT INTO salary_history (history_id,employee_id,old_salary,new_salary,change_date,change_reason,effective_date) VALUES (salary_history_seq.NEXTVAL,g_salary_changes(i).employee_id,g_salary_changes(i).old_salary,g_salary_changes(i).new_salary,SYSDATE,'系统更新',SYSDATE);v_total_old_salary := v_total_old_salary + g_salary_changes(i).old_salary;v_total_new_salary := v_total_new_salary + g_salary_changes(i).new_salary;END LOOP;-- 计算统计信息IF v_total_old_salary > 0 THENv_avg_increase_pct := ROUND((v_total_new_salary - v_total_old_salary) / v_total_old_salary * 100, 2);END IF;DBMS_OUTPUT.PUT_LINE('=== 工资更新完成 ===');DBMS_OUTPUT.PUT_LINE('更新员工数: ' || g_change_count);DBMS_OUTPUT.PUT_LINE('总原工资: $' || v_total_old_salary);DBMS_OUTPUT.PUT_LINE('总新工资: $' || v_total_new_salary);DBMS_OUTPUT.PUT_LINE('平均增长: ' || NVL(v_avg_increase_pct, 0) || '%');END AFTER STATEMENT;END trg_salary_management;
/-- 测试复合触发器
-- 插入测试数据
INSERT INTO employees_demo (employee_id, first_name, last_name, email, hire_date, job_id, salary)
VALUES (1003, 'Bob', 'Johnson', 'bob.johnson@company.com', SYSDATE, 'IT_PROG', 5000);INSERT INTO employees_demo (employee_id, first_name, last_name, email, hire_date, job_id, salary)
VALUES (1004, 'Alice', 'Brown', 'alice.brown@company.com', SYSDATE, 'SA_REP', 4500);COMMIT;-- 测试批量工资更新
UPDATE employees_demo 
SET salary = salary * 1.1 
WHERE employee_id IN (1001, 1003, 1004);COMMIT;-- 查看工资历史记录
SELECT * FROM salary_history ORDER BY change_date DESC;

2.2.2 条件触发器

-- 创建条件触发器:只在特定条件下触发
CREATE OR REPLACE TRIGGER trg_high_salary_alertAFTER UPDATE OF salary ON employees_demoFOR EACH ROWWHEN (NEW.salary > 10000) -- 条件:新工资超过10000
DECLAREv_manager_email VARCHAR2(100);v_dept_name VARCHAR2(50);
BEGIN-- 获取部门经理信息(模拟)DBMS_OUTPUT.PUT_LINE('=== 高工资预警 ===');DBMS_OUTPUT.PUT_LINE('员工: ' || :NEW.first_name || ' ' || :NEW.last_name);DBMS_OUTPUT.PUT_LINE('工资: $' || :OLD.salary || ' -> $' || :NEW.salary);DBMS_OUTPUT.PUT_LINE('增长: $' || (:NEW.salary - :OLD.salary));DBMS_OUTPUT.PUT_LINE('触发高工资预警,需要管理层审批');-- 记录到预警表INSERT INTO salary_alerts (alert_id,employee_id,old_salary,new_salary,alert_date,alert_type,status) VALUES (salary_alert_seq.NEXTVAL,:NEW.employee_id,:OLD.salary,:NEW.salary,SYSDATE,'HIGH_SALARY','PENDING');EXCEPTIONWHEN OTHERS THEN-- 创建预警表(如果不存在)EXECUTE IMMEDIATE 'CREATE TABLE salary_alerts (alert_id NUMBER PRIMARY KEY,employee_id NUMBER,old_salary NUMBER,new_salary NUMBER,alert_date DATE,alert_type VARCHAR2(20),status VARCHAR2(20))';EXECUTE IMMEDIATE 'CREATE SEQUENCE salary_alert_seq START WITH 1 INCREMENT BY 1';-- 重新插入记录INSERT INTO salary_alerts VALUES (1, :NEW.employee_id, :OLD.salary, :NEW.salary, SYSDATE, 'HIGH_SALARY', 'PENDING');
END;
/-- 测试条件触发器
UPDATE employees_demo SET salary = 12000 WHERE employee_id = 1001; -- 触发
UPDATE employees_demo SET salary = 8000 WHERE employee_id = 1003;  -- 不触发COMMIT;

3. INSTEAD OF 触发器

3.1 视图上的INSTEAD OF触发器

3.1.1 复杂视图的DML操作

-- 创建复杂视图
CREATE OR REPLACE VIEW employee_dept_view AS
SELECT e.employee_id,e.first_name,e.last_name,e.email,e.salary,e.hire_date,d.department_id,d.department_name,d.location_id,l.city,l.country_id
FROM employees_demo e
JOIN departments d ON e.department_id = d.department_id
JOIN locations l ON d.location_id = l.location_id;-- 创建INSTEAD OF INSERT触发器
CREATE OR REPLACE TRIGGER trg_employee_dept_insertINSTEAD OF INSERT ON employee_dept_viewFOR EACH ROW
DECLAREv_dept_exists NUMBER;v_location_exists NUMBER;
BEGINDBMS_OUTPUT.PUT_LINE('=== INSTEAD OF INSERT 触发器执行 ===');-- 检查部门是否存在SELECT COUNT(*) INTO v_dept_existsFROM departmentsWHERE department_id = :NEW.department_id;IF v_dept_exists = 0 THEN-- 如果部门不存在,先创建部门DBMS_OUTPUT.PUT_LINE('部门不存在,创建新部门: ' || :NEW.department_name);-- 检查位置是否存在SELECT COUNT(*) INTO v_location_existsFROM locationsWHERE location_id = :NEW.location_id;IF v_location_exists = 0 THEN-- 创建位置(简化处理)INSERT INTO locations (location_id, city, country_id)VALUES (:NEW.location_id, :NEW.city, :NEW.country_id);DBMS_OUTPUT.PUT_LINE('创建新位置: ' || :NEW.city);END IF;-- 创建部门INSERT INTO departments (department_id, department_name, location_id)VALUES (:NEW.department_id, :NEW.department_name, :NEW.location_id);END IF;-- 插入员工记录INSERT INTO employees_demo (employee_id, first_name, last_name, email, salary, hire_date, department_id) VALUES (:NEW.employee_id, :NEW.first_name, :NEW.last_name, :NEW.email,:NEW.salary, :NEW.hire_date, :NEW.department_id);DBMS_OUTPUT.PUT_LINE('员工插入成功: ' || :NEW.first_name || ' ' || :NEW.last_name);EXCEPTIONWHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('插入失败: ' || SQLERRM);RAISE;
END;
/-- 创建INSTEAD OF UPDATE触发器
CREATE OR REPLACE TRIGGER trg_employee_dept_updateINSTEAD OF UPDATE ON employee_dept_viewFOR EACH ROW
BEGINDBMS_OUTPUT.PUT_LINE('=== INSTEAD OF UPDATE 触发器执行 ===');-- 更新员工信息UPDATE employees_demoSET first_name = :NEW.first_name,last_name = :NEW.last_name,email = :NEW.email,salary = :NEW.salary,department_id = :NEW.department_idWHERE employee_id = :OLD.employee_id;DBMS_OUTPUT.PUT_LINE('员工更新: ' || SQL%ROWCOUNT || ' 行');-- 如果部门信息有变化,更新部门表IF :OLD.department_name != :NEW.department_name OR:OLD.location_id != :NEW.location_id THENUPDATE departmentsSET department_name = :NEW.department_name,location_id = :NEW.location_idWHERE department_id = :NEW.department_id;DBMS_OUTPUT.PUT_LINE('部门更新: ' || SQL%ROWCOUNT || ' 行');END IF;-- 如果位置信息有变化,更新位置表IF :OLD.city != :NEW.city OR :OLD.country_id != :NEW.country_id THENUPDATE locationsSET city = :NEW.city,country_id = :NEW.country_idWHERE location_id = :NEW.location_id;DBMS_OUTPUT.PUT_LINE('位置更新: ' || SQL%ROWCOUNT || ' 行');END IF;EXCEPTIONWHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('更新失败: ' || SQLERRM);RAISE;
END;
/-- 创建INSTEAD OF DELETE触发器
CREATE OR REPLACE TRIGGER trg_employee_dept_deleteINSTEAD OF DELETE ON employee_dept_viewFOR EACH ROW
DECLAREv_emp_count NUMBER;
BEGINDBMS_OUTPUT.PUT_LINE('=== INSTEAD OF DELETE 触发器执行 ===');-- 删除员工DELETE FROM employees_demoWHERE employee_id = :OLD.employee_id;DBMS_OUTPUT.PUT_LINE('员工删除: ' || :OLD.first_name || ' ' || :OLD.last_name);-- 检查部门是否还有员工SELECT COUNT(*) INTO v_emp_countFROM employees_demoWHERE department_id = :OLD.department_id;IF v_emp_count = 0 THENDBMS_OUTPUT.PUT_LINE('部门 ' || :OLD.department_name || ' 已无员工,考虑删除部门');-- 可以选择删除空部门或保留END IF;EXCEPTIONWHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('删除失败: ' || SQLERRM);RAISE;
END;
/-- 测试INSTEAD OF触发器
-- 通过视图插入数据
INSERT INTO employee_dept_view (employee_id, first_name, last_name, email, salary, hire_date,department_id, department_name, location_id, city, country_id
) VALUES (1005, 'Mike', 'Wilson', 'mike.wilson@company.com', 7000, SYSDATE,999, 'Research Lab', 9999, 'Innovation City', 'US'
);-- 通过视图更新数据
UPDATE employee_dept_view 
SET salary = 7500, department_name = 'Advanced Research Lab'
WHERE employee_id = 1005;-- 查看结果
SELECT * FROM employee_dept_view WHERE employee_id = 1005;COMMIT;

4. DDL触发器

4.1 系统级DDL触发器

4.1.1 数据库对象变更监控

-- 创建DDL审计表
CREATE TABLE ddl_audit_log (audit_id NUMBER PRIMARY KEY,username VARCHAR2(30),object_type VARCHAR2(30),object_name VARCHAR2(128),object_owner VARCHAR2(30),ddl_operation VARCHAR2(30),ddl_text CLOB,client_info VARCHAR2(64),host_name VARCHAR2(64),ip_address VARCHAR2(15),audit_timestamp DATE
);CREATE SEQUENCE ddl_audit_seq START WITH 1 INCREMENT BY 1;-- 创建DDL触发器
CREATE OR REPLACE TRIGGER trg_ddl_auditAFTER DDL ON SCHEMA -- 在当前SCHEMA上的DDL操作后触发
DECLAREv_ddl_text CLOB;v_client_info VARCHAR2(64);v_host_name VARCHAR2(64);v_ip_address VARCHAR2(15);
BEGIN-- 获取DDL语句文本SELECT xmlserialize(content xmlquery('//text()' passing xmltype(ora_sql_txt(1)) returning content)) INTO v_ddl_text FROM dual;-- 获取客户端信息v_client_info := SYS_CONTEXT('USERENV', 'CLIENT_INFO');v_host_name := SYS_CONTEXT('USERENV', 'HOST');v_ip_address := SYS_CONTEXT('USERENV', 'IP_ADDRESS');-- 记录DDL操作INSERT INTO ddl_audit_log (audit_id,username,object_type,object_name,object_owner,ddl_operation,ddl_text,client_info,host_name,ip_address,audit_timestamp) VALUES (ddl_audit_seq.NEXTVAL,ora_login_user,ora_dict_obj_type,ora_dict_obj_name,ora_dict_obj_owner,ora_sysevent,v_ddl_text,v_client_info,v_host_name,v_ip_address,SYSDATE);-- 输出监控信息DBMS_OUTPUT.PUT_LINE('=== DDL操作监控 ===');DBMS_OUTPUT.PUT_LINE('用户: ' || ora_login_user);DBMS_OUTPUT.PUT_LINE('操作: ' || ora_sysevent);DBMS_OUTPUT.PUT_LINE('对象类型: ' || ora_dict_obj_type);DBMS_OUTPUT.PUT_LINE('对象名称: ' || ora_dict_obj_name);DBMS_OUTPUT.PUT_LINE('对象所有者: ' || ora_dict_obj_owner);DBMS_OUTPUT.PUT_LINE('时间: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));EXCEPTIONWHEN OTHERS THEN-- DDL触发器中的异常处理要小心,避免阻止正常DDL操作INSERT INTO ddl_audit_log (audit_id, username, ddl_operation, ddl_text, audit_timestamp) VALUES (ddl_audit_seq.NEXTVAL, USER, 'ERROR', 'DDL audit error: ' || SQLERRM, SYSDATE);COMMIT;
END;
/-- 测试DDL触发器
-- 创建表
CREATE TABLE test_ddl_table (id NUMBER,description VARCHAR2(100)
);-- 修改表
ALTER TABLE test_ddl_table ADD created_date DATE DEFAULT SYSDATE;-- 创建索引
CREATE INDEX idx_test_ddl_id ON test_ddl_table(id);-- 删除对象
DROP INDEX idx_test_ddl_id;
DROP TABLE test_ddl_table;-- 查看DDL审计记录
SELECT username, ddl_operation, object_type, object_name, audit_timestamp
FROM ddl_audit_log
ORDER BY audit_timestamp DESC;

4.1.2 防止意外删除的保护触发器

-- 创建保护重要表的DDL触发器
CREATE OR REPLACE TRIGGER trg_protect_critical_tablesBEFORE DROP ON SCHEMA
DECLAREv_object_name VARCHAR2(128);v_current_time VARCHAR2(10);v_is_protected BOOLEAN := FALSE;-- 定义受保护的表列表TYPE protected_tables_type IS TABLE OF VARCHAR2(128);protected_tables protected_tables_type := protected_tables_type('EMPLOYEES_DEMO','EMPLOYEE_AUDIT', 'SALARY_HISTORY','DDL_AUDIT_LOG');BEGINv_object_name := ora_dict_obj_name;v_current_time := TO_CHAR(SYSDATE, 'HH24:MI:SS');-- 检查是否为受保护的表FOR i IN 1..protected_tables.COUNT LOOPIF UPPER(v_object_name) = protected_tables(i) THENv_is_protected := TRUE;EXIT;END IF;END LOOP;IF v_is_protected THEN-- 在工作时间(9:00-18:00)禁止删除重要表IF TO_NUMBER(TO_CHAR(SYSDATE, 'HH24')) BETWEEN 9 AND 18 THENRAISE_APPLICATION_ERROR(-20100, '禁止在工作时间(9:00-18:00)删除重要表: ' || v_object_name);END IF;-- 记录删除尝试INSERT INTO ddl_audit_log (audit_id, username, object_type, object_name, ddl_operation,ddl_text, audit_timestamp) VALUES (ddl_audit_seq.NEXTVAL, USER, ora_dict_obj_type, v_object_name, 'DROP_ATTEMPT','尝试删除受保护的表: ' || v_object_name, SYSDATE);COMMIT;-- 发出警告但允许删除(非工作时间)DBMS_OUTPUT.PUT_LINE('警告: 正在删除重要表 ' || v_object_name);DBMS_OUTPUT.PUT_LINE('删除时间: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));DBMS_OUTPUT.PUT_LINE('操作用户: ' || USER);END IF;EXCEPTIONWHEN OTHERS THEN-- 记录错误但不阻止操作NULL;
END;
/-- 测试保护触发器
-- 创建测试表
CREATE TABLE test_protected_table AS SELECT * FROM employees_demo WHERE 1=0;-- 尝试删除(应该成功,因为不在保护列表中)
DROP TABLE test_protected_table;-- 尝试删除受保护的表(在工作时间会被阻止)
-- DROP TABLE employees_demo; -- 取消注释来测试

5. 系统事件触发器

5.1 登录和注销触发器

5.1.1 用户会话监控

-- 创建会话监控表
CREATE TABLE user_session_log (log_id NUMBER PRIMARY KEY,username VARCHAR2(30),session_id NUMBER,host_name VARCHAR2(64),ip_address VARCHAR2(15),program VARCHAR2(64),module VARCHAR2(64),login_time DATE,logout_time DATE,session_duration NUMBER, -- 分钟status VARCHAR2(20)
);CREATE SEQUENCE session_log_seq START WITH 1 INCREMENT BY 1;-- 创建登录触发器
CREATE OR REPLACE TRIGGER trg_user_loginAFTER LOGON ON SCHEMA
DECLAREv_session_id NUMBER;v_host_name VARCHAR2(64);v_ip_address VARCHAR2(15);v_program VARCHAR2(64);v_module VARCHAR2(64);
BEGIN-- 获取会话信息v_session_id := SYS_CONTEXT('USERENV', 'SESSIONID');v_host_name := SYS_CONTEXT('USERENV', 'HOST');v_ip_address := SYS_CONTEXT('USERENV', 'IP_ADDRESS');v_program := SYS_CONTEXT('USERENV', 'PROGRAM');v_module := SYS_CONTEXT('USERENV', 'MODULE');-- 记录登录信息INSERT INTO user_session_log (log_id,username,session_id,host_name,ip_address,program,module,login_time,status) VALUES (session_log_seq.NEXTVAL,USER,v_session_id,v_host_name,v_ip_address,v_program,v_module,SYSDATE,'ACTIVE');COMMIT;-- 检查是否为可疑登录DECLAREv_recent_logins NUMBER;v_different_hosts NUMBER;BEGIN-- 检查最近1小时内的登录次数SELECT COUNT(*) INTO v_recent_loginsFROM user_session_logWHERE username = USERAND login_time >= SYSDATE - 1/24;-- 检查是否从不同主机登录SELECT COUNT(DISTINCT host_name) INTO v_different_hostsFROM user_session_logWHERE username = USERAND login_time >= SYSDATE - 1/24;-- 可疑活动警报IF v_recent_logins > 10 THENDBMS_OUTPUT.PUT_LINE('警告: 用户 ' || USER || ' 1小时内登录超过10次');END IF;IF v_different_hosts > 3 THENDBMS_OUTPUT.PUT_LINE('警告: 用户 ' || USER || ' 从多个不同主机登录');END IF;END;EXCEPTIONWHEN OTHERS THEN-- 登录触发器不应阻止用户登录NULL;
END;
/-- 创建注销触发器
CREATE OR REPLACE TRIGGER trg_user_logoutBEFORE LOGOFF ON SCHEMA
DECLAREv_session_id NUMBER;v_login_time DATE;v_duration NUMBER;
BEGINv_session_id := SYS_CONTEXT('USERENV', 'SESSIONID');-- 获取登录时间SELECT login_time INTO v_login_timeFROM user_session_logWHERE session_id = v_session_idAND username = USERAND status = 'ACTIVE'AND ROWNUM = 1ORDER BY login_time DESC;-- 计算会话持续时间(分钟)v_duration := ROUND((SYSDATE - v_login_time) * 24 * 60, 2);-- 更新注销信息UPDATE user_session_logSET logout_time = SYSDATE,session_duration = v_duration,status = 'COMPLETED'WHERE session_id = v_session_idAND username = USERAND status = 'ACTIVE';COMMIT;EXCEPTIONWHEN NO_DATA_FOUND THEN-- 可能是登录触发器未记录的会话INSERT INTO user_session_log (log_id, username, session_id, logout_time, status) VALUES (session_log_seq.NEXTVAL, USER, v_session_id, SYSDATE, 'LOGOUT_ONLY');COMMIT;WHEN OTHERS THEN-- 注销触发器也不应阻止用户注销NULL;
END;
/-- 创建会话统计查询
CREATE OR REPLACE PROCEDURE show_session_statistics(p_username IN VARCHAR2 DEFAULT NULL,p_days_back IN NUMBER DEFAULT 7
)
AS
BEGINDBMS_OUTPUT.PUT_LINE('=== 用户会话统计 ===');DBMS_OUTPUT.PUT_LINE('统计期间: 最近 ' || p_days_back || ' 天');IF p_username IS NOT NULL THENDBMS_OUTPUT.PUT_LINE('用户: ' || p_username);END IF;DBMS_OUTPUT.PUT_LINE('');-- 显示会话统计FOR rec IN (SELECT username,COUNT(*) AS total_sessions,ROUND(AVG(session_duration), 2) AS avg_duration_min,MAX(session_duration) AS max_duration_min,COUNT(DISTINCT host_name) AS unique_hosts,MAX(login_time) AS last_loginFROM user_session_logWHERE (p_username IS NULL OR username = p_username)AND login_time >= SYSDATE - p_days_backGROUP BY usernameORDER BY total_sessions DESC) LOOPDBMS_OUTPUT.PUT_LINE('用户: ' || rec.username);DBMS_OUTPUT.PUT_LINE('  总会话数: ' || rec.total_sessions);DBMS_OUTPUT.PUT_LINE('  平均时长: ' || NVL(rec.avg_duration_min, 0) || ' 分钟');DBMS_OUTPUT.PUT_LINE('  最长时长: ' || NVL(rec.max_duration_min, 0) || ' 分钟');DBMS_OUTPUT.PUT_LINE('  不同主机: ' || rec.unique_hosts);DBMS_OUTPUT.PUT_LINE('  最后登录: ' || TO_CHAR(rec.last_login, 'YYYY-MM-DD HH24:MI:SS'));DBMS_OUTPUT.PUT_LINE('---');END LOOP;
END;
/-- 查看会话监控结果
SELECT * FROM user_session_log ORDER BY login_time DESC;-- 执行统计查询
EXEC show_session_statistics;

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/84206.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/84206.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/84206.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MTK-Android12-13 Camera2 设置默认视频画质功能实现

MTK-Android12-13 Camera2 设置默认视频画质功能实现 场景&#xff1a;部分客户使用自己的mipi相机安装到我们主板上&#xff0c;最大分辨率为1280720&#xff0c;但是视频画质默认的是640480。实际场景中&#xff0c;在默认视频分辨率情况下拍出来的视频比较模糊、预览也不清晰…

QtDBus模块功能及架构解析

Qt 6.0 中的 QtDBus 模块是一个用于进程间通信&#xff08;IPC&#xff09;的核心模块&#xff0c;它基于 D-Bus 协议实现。D-Bus 是一种在 Linux 和其他类 Unix 系统上广泛使用的消息总线系统&#xff0c;允许应用程序和服务相互通信。 一、QtDBus模块主要功能&#xff1a; 1…

Spring AI 项目实战(六):Spring Boot + AI + DeepSeek 打造智能成语接龙游戏(附完整源码)

系列文章 序号文章名称1Spring AI 项目实战(一):Spring AI 核心模块入门2Spring AI 项目实战(二):Spring Boot + AI + DeepSeek 深度实战(附完整源码)3Spring AI 项目实战(三):Spring Boot + AI + DeepSeek 打造智能客服系统(附完整源码)4Spring AI 项目实战(四…

【HarmonyOS 5】教育开发实践详解以及详细代码案例

以下是基于 ‌HarmonyOS 5‌ 的教育应用开发实践详解及核心代码案例&#xff0c;结合分布式能力与教育场景需求设计&#xff1a; 一、教育应用核心开发技术 ‌ArkTS声明式UI‌ 使用 State 管理学习进度状态&#xff0c;LocalStorageProp 实现跨页面数据同步&#xff08;如课程…

【鸿蒙在 ETS (Extendable TypeScript) 中创建多级目录或文件,可以使用鸿蒙的文件系统 API】

鸿蒙在 ETS (Extendable TypeScript) 中创建多级目录或文件&#xff0c;可以使用鸿蒙的文件系统 API。 // 导入需要的模块 import fs from ohos.file.fs;const TAG"Index" Entry Component struct Index {State message: string Hello World;build() {Row() {Colum…

11. vue pinia 和react redux、jotai对比

对比 Vue 的 Pinia&#xff0c;和 React 的 Redux、Jotai&#xff0c;分中英文简要介绍、特性、底层原理、使用场景。 简单介绍 1.1 Pinia&#xff08;Vue&#xff09; • 英文&#xff1a;Pinia is the official state management library for Vue 3, designed to be simple…

OPenCV CUDA模块目标检测----- HOG 特征提取和目标检测类cv::cuda::HOG

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::cuda::HOG 是 OpenCV 的 CUDA 模块中对 HOG 特征提取和目标检测 提供的 GPU 实现。它与 CPU 版本的 cv::HOGDescriptor 类似&#xff0c;但利…

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…

【QT】QT多语言切换

QT多语言切换 1.创建任意一个项目2. 利用lupdate&#xff08;language update&#xff09;工具生成.ts文件2.1 在工程中的.pro文件中指定.ts文件要存放的位置2.2 选择工具--》外部--》Qt语言家--》更新翻译 3. 利用 lrelease&#xff08;Language Release&#xff09;将 .ts 文…

【差分】详解二维前缀和和差分问题

文章目录 1. 二维前缀和2. 公式推导3. LeetCode 304 二维区域和检索 - 矩阵不可变3.1 304 二维区域和检索 - 矩阵不可变3.2 LeetCode 1139 最大的以 1 为边界的正方形 4. 二维差分问题5. 二维差分的原理以及差分数组计算6. 题目6.1 牛客二维差分6.2 LeetCode 2132. 用邮票贴满网…

Unity 大型手游碰撞性能优化指南

Unity 大型手游碰撞性能优化指南 版本: 2.1 作者: Unity性能优化团队 语言: 中文 前言 在Unity大型手游的开发征途中,碰撞检测如同一位隐形的舞者,它在游戏的物理世界中赋予物体交互的灵魂。然而,当这位舞者的舞步变得繁复冗余时,便会悄然消耗宝贵的计算资源,导致帧率下…

【hive】函数集锦:窗口函数、列转行、日期函数

窗口函数 https://www.cnblogs.com/Uni-Hoang/p/17411313.html <窗口函数> OVER ([PARTITION BY <分组列> [, <分组列>...]][ORDER BY <排序列> [ASC | DESC] [, <排序列> [ASC | DESC]]...][<rows or range clause>]) )窗口函数主要是…

DAY 25 异常处理

目录 DAY 25 异常处理1.异常处理机制2.debug过程中的各类报错3.try-except机制4.try-except-else-finally机制作业&#xff1a;理解今日的内容即可&#xff0c;可以检查自己过去借助ai写的代码是否带有try-except机制&#xff0c;以后可以尝试采用这类写法增加代码健壮性。 DAY…

几何绘图与三角函数计算应用

几何绘图与三角函数计算应用 设计思路 左侧为绘图控制面板&#xff0c;右侧为绘图区域支持绘制点、线、矩形、圆、多边形等基本几何图形实现三角函数计算器&#xff08;正弦、余弦、正切等&#xff09;包含角度/弧度切换和常用数学常数历史记录功能保存用户绘图 完整实现代码…

CSS 定位:原理 + 场景 + 示例全解析

一. 什么是CSS定位? CSS中的position属性用于设置元素的定位方式,它决定了元素在页面中的"定位行为" 为什么需要定位? 常规布局(如 display: block)适用于主结构 定位适用于浮动按钮,弹出层,粘性标题等场景帮助我们精确控制元素在页面中的位置 二. 定位类型全…

GESP 二级复习参考 A

本教程完整包含&#xff1a; 5000字详细知识点解析 36个Python/C双语言示例 15个GESP真题及模拟题 8张专业图表和流程图 # C编程二级标准终极教程## 一、计算机存储系统深度解析### 1.1 存储体系架构 mermaid graph TDA[CPU寄存器] --> B[L1缓存 1-2ns]B --> C[L2缓…

嵌入式面试常问问题

以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…

【人工智能 | 项目开发】Python Flask实现本地AI大模型可视化界面

文末获取项目源码。 文章目录 项目背景项目结构app.py(后端服务)index.html(前端界面)项目运行项目图示项目源码项目背景 随着人工智能技术的快速发展,大语言模型在智能交互领域展现出巨大潜力。本项目基于 Qwen3-1.7B 模型,搭建一个轻量化的智能聊天助手,旨在为用户提…

【设计模式】1.简单工厂、工厂、抽象工厂模式

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 以下是 简单工厂模式、工厂方法模式 和 抽象工厂模式 的 Python 实现与对比&#xff0c;结合代码示例和实际应用场景说明&#xff1a; 1. 简单工厂模式&a…

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…