Bo's Oracle Station

【博客文章2026】MySQL 9.x数据库管理技巧11:触发器、基于值的审计和事件---与Oracle数据库作对比

2026-4-7 19:25| 发布者: admin| 查看: 350| 评论: 0|原作者: Bo Tang

摘要: 本文深入探讨了MySQL 9.6与Oracle 19c数据库在触发器实现基于值的审计以及定时任务调度两个核心功能上的技术差异与实践方法。通过具体的代码示例和完整的测试验证,展示了两种数据库在数据审计和会话管理方面的不同实现哲学。两种数据库在审计触发器实现上殊途同归,Oracle的整合式触发器设计更为简洁高效,而MySQL的分立式触发器更易于理解和维护。在定时任务方面,Oracle的DBMS_SCHEDULER提供了企业级的调度框架,功能远超MySQL的事件调度器,但同时也带来了更高的学习曲线和配置复杂度。
【博客文章2026】MySQL 9.x数据库管理技巧11:触发器、基于值的审计和事件---与Oracle数据库作对比


Author: Bo Tang
1. MySQL数据库的触发器与Oracle数据库的触发器作对比

1.1 在MySQL 9.6数据库中创建DML型的触发器,在world_y数据库中实现基于值的审计:
1.1.1 首先创建一个用于存放旧值的表:

[root@station95 ~]# mysql -u root -p 
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 21
Server version: 9.6.0 MySQL Community Server - GPL

Copyright (c) 2000, 2026, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use world_y;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> create table audit_country(Code char(3),name char(52), capital int,
    ->  Code2 char(2),  action varchar(10), action_time timestamp);
Query OK, 0 rows affected (0.021 sec)

 1.1.2 创建update触发器,实现基于值的审计(记录变更前的值):

mysql> DELIMITER //
mysql> CREATE TRIGGER audit_country_update
    -> AFTER UPDATE ON country
    -> FOR EACH ROW
    -> BEGIN
    ->     IF OLD.code <> NEW.code 
    ->        OR OLD.name <> NEW.name 
    ->        OR OLD.capital <> NEW.capital 
    ->        OR OLD.code2 <> NEW.code2 THEN        
    ->         INSERT INTO audit_country (code, name, capital, code2, action, action_time) 
    ->         VALUES (OLD.code, OLD.name, OLD.capital, OLD.code2, 'UPDATE', NOW());
    ->     END IF;
    -> END//
Query OK, 0 rows affected (0.019 sec)

mysql> DELIMITER ;

    使用“show triggers”命令来查看MySQL数据库里的触发器的状态信息:

mysql> show triggers\G
*************************** 1. row ***************************
             Trigger: audit_country_update
               Event: UPDATE
               Table: country
           Statement: BEGIN
    IF OLD.code <> NEW.code 
       OR OLD.name <> NEW.name 
       OR OLD.capital <> NEW.capital 
       OR OLD.code2 <> NEW.code2 THEN        
        INSERT INTO audit_country (code, name, capital, code2, action, action_time) 
        VALUES (OLD.code, OLD.name, OLD.capital, OLD.code2, 'UPDATE', NOW());
    END IF;
END
              Timing: AFTER
             Created: 2026-04-10 17:23:24.83
            sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
             Definer: root@localhost
character_set_client: utf8mb4
collation_connection: utf8mb4_0900_ai_ci
  Database Collation: utf8mb4_0900_ai_ci
1 row in set (0.003 sec)

    测试更新操作

mysql> update country set Capital=9999 where Code='ZWE';
Query OK, 1 row affected (0.005 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> show status like 'auto_commit';
Empty set (0.049 sec)

mysql> commit;
Query OK, 0 rows affected (0.000 sec)

mysql> select * from country where Code='ZWE';
+------+----------+---------+-------+
| Code | Name     | Capital | Code2 |
+------+----------+---------+-------+
| ZWE  | Zimbabwe |    9999 | ZW    |
+------+----------+---------+-------+
1 row in set (0.001 sec)

    查看基于值的审计的结果

mysql>  select * from audit_country;
+------+----------+---------+-------+--------+---------------------+
| Code | name     | capital | Code2 | action | action_time         |
+------+----------+---------+-------+--------+---------------------+
| ZWE  | Zimbabwe |    4068 | ZW    | UPDATE | 2026-04-10 18:29:51 |
+------+----------+---------+-------+--------+---------------------+
1 row in set (0.001 sec)

1.1.2 创建delete触发器,实现基于值的审计(记录删除前的值):

mysql> DELIMITER //
mysql> CREATE TRIGGER audit_country_delete
    -> AFTER DELETE ON country
    -> FOR EACH ROW
    -> BEGIN
    ->     INSERT INTO audit_country (code, name, capital, code2, action, action_time) 
    ->     VALUES (OLD.code, OLD.name, OLD.capital, OLD.code2, 'DELETE', NOW());
    -> END//
Query OK, 0 rows affected (0.007 sec)

mysql> DELIMITER ;

    使用“show triggers”命令来查看MySQL数据库里的触发器的状态信息:

mysql> show triggers\G
......
*************************** 2. row ***************************
             Trigger: audit_country_delete
               Event: DELETE
               Table: country
           Statement: BEGIN
    INSERT INTO audit_country (code, name, capital, code2, action, action_time) 
    VALUES (OLD.code, OLD.name, OLD.capital, OLD.code2, 'DELETE', NOW());
END
              Timing: AFTER
             Created: 2026-04-10 22:49:53.59
            sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
             Definer: root@localhost
character_set_client: utf8mb4
collation_connection: utf8mb4_0900_ai_ci
  Database Collation: utf8mb4_0900_ai_ci
2 rows in set (0.003 sec)

    测试删除操作

mysql> delete from countrylanguage where CountryCode='ZWE';
Query OK, 4 rows affected (0.002 sec)

mysql> delete from country where Code='ZWE';
Query OK, 1 row affected (0.002 sec)

mysql> select * from country where Code='ZWE';
Empty set (0.001 sec)

    查看基于值的审计的结果

mysql> select * from audit_country;
+------+----------+---------+-------+--------+---------------------+
| Code | name     | capital | Code2 | action | action_time         |
+------+----------+---------+-------+--------+---------------------+
| ZWE  | Zimbabwe |    4068 | ZW    | UPDATE | 2026-04-10 18:29:51 |
| ZWE  | Zimbabwe |    9999 | ZW    | DELETE | 2026-04-10 23:06:08 |
+------+----------+---------+-------+--------+---------------------+
2 rows in set (0.001 sec)

1.2 在Oracle数据库(19.3.0)中DML型的触发器,实现基于值的审计:
    由于在上一篇博客中已数据从MySQL数据库导出并导入Oracle数据库,所以我们可以在Oracle数据库作如下对比实验,创建功能相同的DML型的触发器,实现基于值的审计:
1.2.1 首先创建一个用于存放旧值的表:

[oracle@classroom ~]$ sqlplus /nolog

SQL*Plus: Release 19.0.0.0.0 - Production on Fri Apr 10 23:22:18 2026
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.

SQL> conn hr/oracle_4U
Connected.
SQL> create table audit_country(Code char(3),name char(52), capital int,
  2  Code2 char(2),  action varchar(10), action_time timestamp);

Table created.

1.2.2 创建触发器,实现基于值的审计(记录变更前的值和删除前的值):
    Oracle可以把update和delete同时进行审计:

SQL> CREATE OR REPLACE TRIGGER audit_country_trg
  2  AFTER UPDATE OR DELETE ON country
  3  REFERENCING NEW AS NEW OLD AS OLD
  4  FOR EACH ROW
  5  BEGIN
  6      IF UPDATING AND (:OLD.code  <> :NEW.code 
  7                                   OR :OLD.name <> :NEW.name
  8                                   OR :OLD.capital  <> :NEW.capital
  9                                   OR :OLD.code2 <> :NEW.code2) 
 10       THEN
 11          INSERT INTO audit_country (code, name, capital, code2, action, action_time)  
 12          VALUES (:OLD.code, :OLD.name, :OLD.capital, :OLD.code2, 'UPDATE', systimestamp);
 13      ELSIF DELETING THEN
 14           INSERT INTO audit_country (code, name, capital, code2, action, action_time)  
 15          VALUES (:OLD.code, :OLD.name, :OLD.capital, :OLD.code2, 'DELETE', systimestamp);
 16      END IF;
 17  END;
 18  /

Trigger created.

      使用dba_trigger来查看Oracle数据库里的触发器的状态信息:

SQL> set linesize 1000
SQL> col TRIGGER_NAME format a15 trunc 
SQL> col TRIGGERING_EVENT format a20 trunc
SQL> col WHEN_CLAUSE format a20 trunc
SQL> col TABLE_OWNER format a10 trunc
SQL> col COLUMN_NAME format a10 trunc
SQL>  select TRIGGER_NAME,TRIGGER_TYPE,TRIGGERING_EVENT,TABLE_OWNER,COLUMN_NAME, status, ACTION_TYPE from user_triggers where trigger_name='AUDIT_COUNTRY_TRG';

TRIGGER_NAME    TRIGGER_TYPE     TRIGGERING_EVENT     TABLE_OWNE COLUMN_NAM STATUS   ACTION_TYPE
--------------- ---------------- -------------------- ---------- ---------- -------- -----------
AUDIT_COUNTRY_T AFTER EACH ROW   UPDATE OR DELETE     HR                    ENABLED  PL/SQL

    测试更新操作

SQL>  update country set Capital=9999 where Code='ZWE';

1 row updated.

SQL> commit;

Commit complete.

SQL> select * from country where Code='ZWE';

COD NAME                                                    CAPITAL CO
--- ---------------------------------------------------- ---------- --
ZWE Zimbabwe                                                   9999 ZW

    查看基于值的审计的结果

SQL> select * from audit_country;  

COD NAME                                                    CAPITAL CO ACTION     ACTION_TIME
--- ---------------------------------------------------- ---------- -- ---------- ---------------------------------------------------------------------------
ZWE Zimbabwe                                                   4068 ZW UPDATE     11-APR-26 05.33.49.772917 A
 
      测试删除操作

SQL>  delete from countrylanguage where CountryCode='ZWE';

4 rows deleted.

SQL>  delete from country where Code='ZWE';

1 row deleted.

SQL> commit;

Commit complete.

SQL> select * from country where Code='ZWE';

no rows selected

    查看基于值的审计的结果

SQL> select * from audit_country;

COD NAME                                                    CAPITAL CO ACTION     ACTION_TIME
--- ---------------------------------------------------- ---------- -- ---------- ---------------------------------------------------------------------------
ZWE Zimbabwe                                                   4068 ZW UPDATE     11-APR-26 05.33.49.772917 AM
ZWE Zimbabwe                                                   9999 ZW DELETE     11-APR-26 05.41.25.311883 AM

2. MySQL数据库的事件与Oracle数据库的Scheduler作对比

    MySQL EVENT在MySQL事件调度器(Event Scheduler)线程中运行,语法简单,可内嵌SQL语句或调用存储过程。
2.1 在MySQL 9.6数据库中创建事件,用于控制test用户的需要长时间运行的SQL语句:

mysql> DELIMITER //
mysql> CREATE EVENT ctrl_long_session ON SCHEDULE EVERY 15 SECOND
    -> DO
    -> BEGIN
    ->   DECLARE var_pid INT;
    ->   DECLARE cur_process CURSOR FOR SELECT id
    ->     FROM INFORMATION_SCHEMA.PROCESSLIST WHERE TIME>30 AND USER = 'test';
    ->   OPEN cur_process;
    ->     BEGIN
    ->   DECLARE EXIT HANDLER FOR NOT FOUND
    ->     BEGIN
    ->       CLOSE cur_process;
    ->     END;
    ->   LOOP
    ->     FETCH cur_process INTO var_pid;
    ->     kill var_pid;
    ->   END LOOP;
    -> END;
    -> END//

Query OK, 0 rows affected (0.013 sec)

    使用“show create event”命令来查看MySQL数据库里的事件的DDL信息:

mysql> show create event ctrl_long_session\G
*************************** 1. row ***************************
               Event: ctrl_long_session
            sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
           time_zone: SYSTEM
        Create Event: CREATE DEFINER=`root`@`localhost` EVENT `ctrl_long_session` ON SCHEDULE EVERY 15 SECOND STARTS '2026-04-11 19:22:16' ON COMPLETION NOT PRESERVE ENABLE DO BEGIN
  DECLARE var_pid INT;
  DECLARE cur_process CURSOR FOR SELECT id
    FROM INFORMATION_SCHEMA.PROCESSLIST WHERE TIME>30 AND USER = 'test';
  OPEN cur_process;
    BEGIN
  DECLARE EXIT HANDLER FOR NOT FOUND
    BEGIN
      CLOSE cur_process;
    END;
  LOOP
    FETCH cur_process INTO var_pid;
    kill var_pid;
  END LOOP;
END;
END
character_set_client: utf8mb4
collation_connection: utf8mb4_0900_ai_ci
  Database Collation: utf8mb4_0900_ai_ci
1 row in set (0.001 sec)

    测该试事件,用来控制test用户的长时间运行的SQL语句
    打开MySQL事件功能:

mysql> set global event_scheduler=on;
Query OK, 0 rows affected (0.001 sec)

    创建测试用户

mysql> create user 'test'@'localhost'identified by 'oracle_4U';
Query OK, 0 rows affected (0.019 sec)

mysql> grant select  on world_y.* to 'test'@'localhost';
Query OK, 0 rows affected (0.002 sec)

    执行需要长时间运行的SQL语句
    由于运行了超过30秒的SQL语句,再加上观察窗口的15秒,所以如果执行等于或超过46秒的SQL语句,必然会被事件ctrl_long_session控制到:

[root@station95 ~]# mysql -u test -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1627
Server version: 9.6.0 MySQL Community Server - GPL

Copyright (c) 2000, 2026, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use world_y;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select sleep(46);
ERROR 2013 (HY000): Lost connection to MySQL server during query
No connection. Trying to reconnect...
Connection id:    1633
Current database: world_y

ERROR 2013 (HY000): Lost connection to MySQL server during query
No connection. Trying to reconnect...
Connection id:    1637
Current database: world_y

ERROR 2013 (HY000): Lost connection to MySQL server during query
   
2.2 在Oracle数据库中创建Scheduler,用于控制hr用户的需要长时间运行的SQL语句:
    在Oracle 数据库中,MySQL的CREATE EVENT没有完全一一对应的功能,但最接近的替代或类比对象是DBMS_SCHEDULER。DBMS_SCHEDULER创建Schedule job,用于在数据库内部按预定时间执行存储过程或PL/SQL代码块。与MySQL数据库的create event相比,Oracle DBMS_SCHEDULER功能强大得多,是Oracle企业调度框架的核心。虽然语法和功能比MySQL复杂,但基本概念一致:按计划执行任务。
    我们可以在Oracle数据库作如下对比实验:创建功能相同的Scheduler Job,用于控制hr用户的需要长时间运行的SQL语句:
    首先以sys用户创建存储过程,用于kill session:

[oracle@classroom ~]$ sqlplus /nolog

SQL*Plus: Release 19.0.0.0.0 - Production on Sun Apr 12 05:33:08 2026
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.

SQL> conn / as sysdba
Connected.
SQL> CREATE OR REPLACE PROCEDURE kill_long_sessions 
  2  is
  3      CURSOR cur_process IS
  4          SELECT sid, serial#
  5          FROM v$session
  6          WHERE last_call_et > 30 
  7            AND username = 'HR' 
  8            AND status = 'ACTIVE' 
  9            AND type != 'BACKGROUND'; 
 10  BEGIN
 11      FOR rec IN cur_process 
 12      LOOP
 13          BEGIN
 14          EXECUTE IMMEDIATE 'ALTER SYSTEM KILL SESSION ''' ||rec.sid || ',' || rec.serial# ||''' IMMEDIATE';         
 15          EXCEPTION
 16              WHEN OTHERS THEN
 17                  exit;
 18          END;
 19      END LOOP;
 20      IF cur_process%NOTFOUND THEN
 21          DBMS_OUTPUT.put_line('No long running sessions found for user HR');
 22      END IF;
 23  EXCEPTION
 24      WHEN OTHERS THEN
 25          DBMS_OUTPUT.put_line('Error in kill_long_sessions: ' || SQLERRM);
 26          RAISE;
 27  END kill_long_sessions;
 28  /

Procedure created.

    接着,以sys用户使用dbms_scheduler创建作业。作业内部调用上面的kill_long_session存储过程:

SQL> BEGIN
  2      DBMS_SCHEDULER.CREATE_JOB (
  3          job_name        => 'CTRL_LONG_SESSION_JOB',
  4          job_type        => 'PLSQL_BLOCK',
  5          job_action      => 'BEGIN kill_long_sessions; END;',
  6          start_date      => SYSTIMESTAMP,
  7          repeat_interval => 'FREQ=SECONDLY; INTERVAL=15',
  8          enabled         => TRUE,
  9          comments        => 'Kill test user sessions running more than 30 seconds'
 10      );
 11  END;
 12  /

PL/SQL procedure successfully completed.

    查看Oracle数据库中的scheduler信息:

SQL> set linesize 1000 
SQL> col job_name format a25 trunc
SQL> SELECT job_name, enabled, state,to_char(last_start_date,'YYYY-MM-DD:HH24:MI:SS'),to_char(next_run_date,'YYYY-MM-DD:HH24:MI:SS')
  2  from dba_scheduler_jobs WHERE job_name = 'CTRL_LONG_SESSION_JOB';

JOB_NAME                  ENABL STATE           TO_CHAR(LAST_START_ TO_CHAR(NEXT_RUN_DA
------------------------- ----- --------------- ------------------- -------------------
CTRL_LONG_SESSION_JOB     TRUE  SCHEDULED       2026-04-12:06:11:01 2026-04-12:06:11:16

    测试该Scheduler Job,用来控制HR用户的长时间运行的SQL语句
    为了测试,授权HR用户执行dbms_lock(对应MySQL数据库中的sleep函数):

SQL> grant execute on dbms_lock to hr;

Grant succeeded.
   
    执行需要长时间运行的SQL语句
    由于运行了超过30秒的SQL语句,再加上观察窗口的15秒,所以如果执行了等于或超过46秒的SQL语句,必然会被Scheduler job ctrl_long_session_job控制到:

SQL> conn hr/oracle_4U
Connected.
SQL> exec DBMS_LOCK.SLEEP(46);
BEGIN DBMS_LOCK.SLEEP(46); END;

      *
ERROR at line 1:
ORA-03113: end-of-file on communication channel
Process ID: 929728
Session ID: 90 Serial number: 640

   

路过

雷人

握手

鲜花

鸡蛋

QQ|手机版|Bo's Oracle Station   

GMT+8, 2026-4-12 20:35 , Processed in 0.088208 second(s), 30 queries .

返回顶部