动态构造 MySQL 创建触发器的代码

Dynamically constructing MySQL Code for creating a trigger

我正在尝试为 MySQL 数据库实施审计 trail/logging。现在我已经创建了一个 "audit" 数据库(我们称之为 company_audit),它将包含主数据库中某些 table 的 "audit" 副本。 (比如 company.customers 我将创建 company_audit.customers。审核 table 将包含与原始 table 中完全相同的列。

我想使用触发器将原始 table 中的更改插入到审计 table 中,如下所示:

DELIMITER $$
DROP TRIGGER IF EXISTS customers_history_AU$$
CREATE TRIGGER customers_history_AU
AFTER UPDATE ON customers
FOR EACH ROW
BEGIN
  INSERT INTO company_audit.customers (customer_id, col2, col3, col4, col5)
  VALUES (OLD.customer_id, OLD.col2, OLD.col3, OLD.col4, OLD.col5);
END$$
DELIMITER ;

现在回答我的问题:如何构建上面列表中的 SQL 字符串(例如,使用存储过程),以便可以动态获取列名(例如,从 information_schema).

我在这里看到了类似的东西:http://uber-code.blogspot.com/2011/02/mysql-audit-logging-triggers.html,但是这里的代码并不是为了从原始 table 中检索列名,或者我可能无法理解它所以成功!

谢谢。

本文介绍了如何在 Sql 服务器中实现动态审核触发器,您可以移植到 MySQL。

https://www.simple-talk.com/sql/database-administration/pop-rivetts-sql-server-faq-no.5-pop-on-the-audit-trail/

没有收到关于这个问题的任何明确的解决方案,我已经着手拼凑出一个概念验证选项(因为 MySQL 本身不会让你 运行 SQL 代码使用准备好的语句创建触发器)。请随时提出任何积极的意见。

DELIMITER //
DROP PROCEDURE IF EXISTS createAuditTable//
CREATE PROCEDURE createAuditTable(tblname CHAR(30), sufftxt CHAR(10), pri CHAR(20), filename CHAR(255) )
BEGIN
    SELECT DATABASE() INTO @dbname;
    SET @srctbl = CONCAT(@dbname, ".", tblname);
    SET @destdb = CONCAT(@dbname, "_", sufftxt);
    SET @desttbl = CONCAT(@destdb, ".", tblname);

    SET @str1 = CONCAT( "CREATE DATABASE IF NOT EXISTS ", @destdb);
    PREPARE stmt1 FROM @str1;
    EXECUTE stmt1;
    DEALLOCATE PREPARE stmt1;

    SET @str2 = "SET FOREIGN_KEY_CHECKS=0";
    PREPARE stmt2 FROM @str2;
    EXECUTE stmt2;
    DEALLOCATE PREPARE stmt2;

    SELECT COUNT(*) FROM information_schema.tables WHERE table_name = tblname AND table_schema = @destdb INTO @tblcount;
    IF (@tblcount = 0) THEN 
        SET @str3 = CONCAT("CREATE TABLE ", @desttbl, " LIKE ", @srctbl);
        PREPARE stmt3 FROM @str3;
        EXECUTE stmt3;
        DEALLOCATE PREPARE stmt3;
    END IF;

    SELECT COUNT(*) FROM information_schema.columns WHERE table_name = tblname AND table_schema = @destdb AND column_key = 'PRI' INTO @keycount;

    IF (@keycount <> 0) THEN 
        SET @str4 = CONCAT("ALTER TABLE ", @desttbl, " DROP PRIMARY KEY, ADD INDEX ", pri, " (", pri, ")" );
        PREPARE stmt4 FROM @str4;
        EXECUTE stmt4;
        DEALLOCATE PREPARE stmt4;
    END IF;

SELECT CONCAT( "DELIMITER $$
DROP TRIGGER IF EXISTS ", tblname, "_history_BU$$
CREATE TRIGGER ", tblname, "_history_BU
BEFORE UPDATE ON ", tblname, "
FOR EACH ROW
BEGIN
    INSERT INTO ", @desttbl, " (",
(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_schema = @dbname AND table_name = tblname), ") ",
    "
    VALUES(", 
(SELECT GROUP_CONCAT('OLD.', column_name) FROM information_schema.columns WHERE table_schema = @dbname AND table_name = tblname),
 ");
END$$
DELIMITER ;"
 ) AS qstr FROM DUAL INTO @triggertxt;

SET @savestr = CONCAT('SELECT ', '"', @triggertxt, '"', " INTO DUMPFILE ", '"', filename, '"');
PREPARE stmt5 FROM @savestr;
EXECUTE stmt5;
DEALLOCATE PREPARE stmt5;


END//
DELIMITER ;  

要使用,请调用程序:

CALL createAuditTable('name_of_table', 'history', 'pri_key_fld', 'path/to/file.sql');

使用当前工作数据库的名称创建了一个新数据库,并附加了“_history”后缀。 table "name_of_table" 是在这个新数据库中创建的,与原来的 table 相同 "pri_key_fld"字段(应该是table"name_of_table"的primary/unique键)转换成普通的"INDEX"键。这样做的目的是为了避免在将来对多行进行审计日志记录时出现独特的违规行为。

THEN 运行 过程创建的文件: SOURCE 'path/to/file.sql'; (或该文件中 运行 SQL 的任何替代语法)

一些注意事项: 现在,您只能为 "pri_key_fld" 提供一个字段。理想情况下,我们希望提供一个包含 table 中所有唯一字段的 "array"。目前,如果您有多个唯一字段,唯一违规将阻止您记录不止一行。那可不好!

同样,通过在磁盘上创建文件的过程显然是非常笨拙和低效的,只是在下一个命令中从同一文件读取 SQL。可以探索改进的一种替代方法是:运行 来自命令行的 CALL createAuditTable 部分,将输出捕获为文本,然后 运行 与 SQL 相同命令行。我确实在 Windows PowerShell 上尝试过;但输出充满了文字“\r\n”字符串(代表换行符)。我没有时间立即清理这个绳子,所以它现在在冰箱里!

最后,各位MySQL忍者们,请多多关照。我不是专业人士,真的。这只是解决实际问题的自己种杂货的尝试。

谢谢。