2013-03-12 64 views
4

假設我們在數據庫中有50 tables,並且我們希望捕獲每個表的列中的所有更改(列的前值和新值)。審計表會在那裏,這將有列如下:創建觸發器以將數據添加到審計表中

IDServer_NameUser_NameDate_TimeTable_NameColumn_NameOld_ValueNew_Value

會有一個審覈表,將捕獲的所有變化來自該數據庫的表格。我相信我們可以爲該數據庫的每個表創建觸發器。但請讓我知道如何將所有數據添加到一個審計表。如果你能爲我提供一個有用的例子。

感謝和問候, 帕塔

回答

13

我可以爲您提供了一種算法,在工作,大部分的基礎工作已經完成:

這可能是你的審計表,應加時間戳列作爲修改日期或更多信息根據您的要求:

CREATE TABLE audit (
    old_data VARCHAR(100), 
    new_data VARCHAR(100), 
    tbl_name VARCHAR(100) 
) 
| 

這可以用作參考觸發器;注意,會有一個單獨的觸發器爲每個表:

CREATE TRIGGER testtrigger BEFORE UPDATE ON <table_name> 
    FOR EACH ROW BEGIN 
    INSERT INTO audit(old_data, new_data, tbl_name) VALUES (OLD.first_name, NEW.first_name, "testtable"); 
    END; 
| 

你可以爲每列多INSERT語句之一。如果你想要把不插入未改變,你可以做在觸發以下更改數據的限制:

IF(OLD.column_name <> NEW.column_name) THEN 
    --Your insert query here 
ELSE 
    --NOOP 
END IF; 

讓我們知道,如果需要更多的信息。

+0

ya ..我需要更多關於這方面的信息...我也想存儲列名和我如何獲得tabel名稱動態 – 2016-04-27 05:36:20

+0

@AnishRai,當你定義表的觸發器,你不會有列與你的細節。在插入審計時使用列名稱。 – 2016-04-27 08:49:21

+0

但..suppose採取一種情況下,我在審計表中存儲操作意味着有一列操作並插入更新first_name如果用戶在這種情況下更新名字,我怎麼知道哪一列由用戶更新 – 2016-04-27 08:55:21

0

我花了幾天的時間想出一個存儲過程來自動/動態地創建MariaDB中的UPDATE/DELETE觸發器(與v 10.1.9協同工作),審計更新和刪除的所有更改。該解決方案使用INFORMATION_SCHEMA爲每個表自動生成審計觸發器。在更新時,只更改已更改的列進行審計,刪除所有歷史記錄時保留在審計中。

在下面的示例中,我們創建了一個包含兩個表tb_company和tb_auditdetail的測試數據庫,它們將存放我們的審計日誌。

-- Dynamic Automated Update/Delete Triggers in MariaDB 
    -- Leonard Tonna 19/05/2016 - www.ilabmalta.com 

    CREATE DATABASE db_ilabmalta_test; 

    USE db_ilabmalta_test; 

    CREATE TABLE tb_auditDetail(
     audit_pk int(9) NOT NULL PRIMARY KEY AUTO_INCREMENT, 
     type varchar(1) NOT NULL, 
     tablename varchar(128) NULL, 
     pk varchar(128) NULL, 
     fieldname varchar(128) NULL, 
     oldvalue varchar(1000) NULL, 
     newvalue varchar(1000) NULL, 
     updatedate datetime NULL, 
     username varchar(128) NULL, 
     dbusername varchar(128) NULL, 
     machinename varchar(128) NULL); 

    CREATE TABLE tb_company(
     cmp_pk int(9) NOT NULL PRIMARY KEY AUTO_INCREMENT, 
     cmp_name varchar(100) NOT NULL, 
     cmp_no varchar(16) NULL, 
     cmp_status smallint NOT NULL DEFAULT 1, 
     cmp_created datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 
     cmp_createdby varchar(10) NOT NULL, 
     cmp_updated datetime NULL, 
     cmp_updatedby varchar(10) NULL, 
     cmp_record_version int(9) NOT NULL DEFAULT 1) ; 

    -- We now create sp_maketrigger which is the stored procedure 
    -- which will give us our trigger scripts 

    DELIMITER $$ 

    DROP PROCEDURE IF EXISTS sp_maketrigger; 

    CREATE PROCEDURE sp_maketrigger (IN s_tablename CHAR(30), OUT u_trigger_out VARCHAR(65500) CHARACTER SET ascii,OUT d_trigger_out VARCHAR(65500) CHARACTER SET ascii) 
    BEGIN 
     DECLARE s_fieldname VARCHAR(50); 
     DECLARE u_trigger VARCHAR(65500) CHARACTER SET ascii; 
     DECLARE d_trigger VARCHAR(65500) CHARACTER SET ascii; 
     DECLARE s_key VARCHAR(50); 
     DECLARE s_updatedby VARCHAR(50); 
     DECLARE s_updated VARCHAR(50); 
     DECLARE s_recversion VARCHAR(50); 
     DECLARE done INT DEFAULT 0; 
     DECLARE cursor_end CONDITION FOR SQLSTATE '02000'; 
     DECLARE col_cursor CURSOR FOR SELECT COLUMN_NAME FROM test_prepare_vw; 
     DECLARE pri_cursor CURSOR FOR SELECT COLUMN_NAME FROM test_prepare_vw2; 
     DECLARE upd_cursor CURSOR FOR SELECT COLUMN_NAME FROM test_prepare_vw3; 
     DECLARE rec_cursor CURSOR FOR SELECT COLUMN_NAME FROM test_prepare_vw4; 
     DECLARE CONTINUE HANDLER FOR cursor_end SET done = 1; 

     DROP VIEW IF EXISTS test_prepare_vw; 
     DROP VIEW IF EXISTS test_prepare_vw2; 
     DROP VIEW IF EXISTS test_prepare_vw3; 
     DROP VIEW IF EXISTS test_prepare_vw4; 

     SET u_trigger = ''; 
     SET u_trigger = CONCAT('DELIMITER $$ \nDROP TRIGGER IF EXISTS tra_',s_tablename,'_update;\n'); 
     SET u_trigger = CONCAT(u_trigger,'CREATE TRIGGER tra_',s_tablename,'_update AFTER UPDATE ON ',s_tablename,' FOR EACH ROW \n'); 
     SET u_trigger = CONCAT(u_trigger,'BEGIN \n'); 
     SET u_trigger = CONCAT(u_trigger,'DECLARE msg VARCHAR(255); \n'); 

     SET d_trigger = ''; 
     SET d_trigger = CONCAT('DELIMITER $$ \nDROP TRIGGER IF EXISTS tra_',s_tablename,'_delete;\n'); 
     SET d_trigger = CONCAT(d_trigger,'CREATE TRIGGER tra_',s_tablename,'_delete AFTER DELETE ON ',s_tablename,' FOR EACH ROW \n'); 
     SET d_trigger = CONCAT(d_trigger,'BEGIN \n'); 

     SET @query = CONCAT('CREATE VIEW test_prepare_vw2 as SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = \'', s_tablename, '\' AND table_schema = \'db_diers\' AND COLUMN_NAME NOT LIKE \'%updated%\' AND COLUMN_KEY = \'PRI\' ORDER BY ORDINAL_POSITION'); 
     PREPARE stmt from @query; 
     EXECUTE stmt; 
     DEALLOCATE PREPARE stmt; 

     OPEN pri_cursor; 
     FETCH pri_cursor INTO s_key; 
     CLOSE pri_cursor; 
     DROP VIEW test_prepare_vw2; 

     SET @query = CONCAT('CREATE VIEW test_prepare_vw3 as SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = \'', s_tablename, '\' AND table_schema = \'db_diers\' AND COLUMN_NAME LIKE \'%updatedby%\' AND COLUMN_KEY <> \'PRI\' ORDER BY ORDINAL_POSITION'); 
     PREPARE stmt from @query; 
     EXECUTE stmt; 
     DEALLOCATE PREPARE stmt; 

     OPEN upd_cursor; 
     FETCH upd_cursor INTO s_updatedby; 
     CLOSE upd_cursor; 
     DROP VIEW test_prepare_vw3; 
     SET s_updated = LEFT(s_updatedby,(LENGTH(RTRIM(s_updatedby)))-2); 

     SET @query = CONCAT('CREATE VIEW test_prepare_vw4 as SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = \'', s_tablename, '\' AND table_schema = \'db_diers\' AND COLUMN_NAME LIKE \'%record_version%\' AND COLUMN_KEY <> \'PRI\' ORDER BY ORDINAL_POSITION'); 
     PREPARE stmt from @query; 
     EXECUTE stmt; 
     DEALLOCATE PREPARE stmt; 

     OPEN rec_cursor; 
     FETCH rec_cursor INTO s_recversion; 
     CLOSE rec_cursor; 
     DROP VIEW test_prepare_vw4; 

     SET @query = CONCAT('CREATE VIEW test_prepare_vw as SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = \'', s_tablename, '\' AND table_schema = \'db_diers\' AND COLUMN_KEY <> \'PRI\' ORDER BY ORDINAL_POSITION'); 
     PREPARE stmt from @query; 
     EXECUTE stmt; 
     DEALLOCATE PREPARE stmt; 

     SET u_trigger = CONCAT(u_trigger,' IF (ISNULL(NEW.',s_recversion,') OR OLD.',s_recversion,' >= NEW.',s_recversion,' OR ISNULL(NEW.',s_updatedby,') OR NEW.',s_updatedby,' = \'\' OR ISNULL(NEW.',s_updated,') OR NEW.',s_updated,' = OLD.',s_updated,') THEN \n'); 
     SET u_trigger = CONCAT(u_trigger,'  set msg = \'Cannot update record without specifying updated/updatedby by columns and without incrementing the record version.\'; \n'); 
     SET u_trigger = CONCAT(u_trigger,'  SIGNAL SQLSTATE \'45000\' SET MESSAGE_TEXT = msg; \n'); 
     SET u_trigger = CONCAT(u_trigger,' END IF;  \n'); 

     OPEN col_cursor; 

     FETCH col_cursor INTO s_fieldname; 
     WHILE done = 0 DO 
      SET u_trigger = CONCAT(u_trigger,' IF (IFNULL(OLD.',s_fieldname,',\'\') <> IFNULL(NEW.',s_fieldname,',\'\')) THEN\n'); 
      SET u_trigger = CONCAT(u_trigger,'  INSERT INTO tb_auditdetail (type, tablename, pk, fieldname, oldvalue, newvalue, updatedate, username, dbusername, machinename) \n'); 
      SET u_trigger = CONCAT(u_trigger,'  VALUES (\'U\', \'',s_tablename,'\', OLD.',s_key,', \'',s_fieldname,'\', OLD.',s_fieldname,', NEW.',s_fieldname,', CURRENT_TIMESTAMP,NEW.',s_updatedby,',CURRENT_USER(),@@hostname);\n'); 
      SET u_trigger = CONCAT(u_trigger,' END IF;\n'); 

      SET d_trigger = CONCAT(d_trigger,'  INSERT INTO tb_auditdetail (type, tablename, pk, fieldname, oldvalue, newvalue, updatedate, username, dbusername, machinename) \n'); 
      SET d_trigger = CONCAT(d_trigger,'  VALUES (\'D\', \'',s_tablename,'\', OLD.',s_key,', \'',s_fieldname,'\', OLD.',s_fieldname,',NULL, CURRENT_TIMESTAMP,NULL,CURRENT_USER(),@@hostname);\n'); 

      FETCH col_cursor INTO s_fieldname; 
     END WHILE; 
     CLOSE col_cursor; 

     DROP VIEW test_prepare_vw; 

     SET u_trigger = CONCAT(u_trigger,'END;$$ \nDELIMITER ; \n'); 
     SET d_trigger = CONCAT(d_trigger,'END;$$ \nDELIMITER ; \n'); 
     SELECT u_trigger INTO u_trigger_out; 
     SELECT d_trigger INTO d_trigger_out; 


    END; $$ 

    DELIMITER ; 

    -- And finally, to extract the Trigger Scripts 

    call sp_maketrigger('tb_company',@s_line1,@d_line1); 

    SELECT CONCAT(@s_line1,@d_line1) 

    -- You just need to copy, paste and execute the trigger script, and 
    -- voila, your audit is in place. 

上面的例子理所當然地與每個表中你有5列:創建,createdby,更新,updatedby,record_version。

但是,您可以以不同方式自定義存儲過程sp_maketrigger以滿足您的需求。 sp也受到增強和改進。