DROP PROCEDURE IF EXISTS CreateAuditTriggers;
DELIMITER //
CREATE PROCEDURE CreateAuditTriggers(IN schemaName VARCHAR(64))
BEGIN
-- Declare variables
DECLARE done INT DEFAULT FALSE;
DECLARE tblName VARCHAR(64);
DECLARE auditTblName VARCHAR(64);
DECLARE colName VARCHAR(64);
DECLARE colType VARCHAR(64);
DECLARE triggerSQL LONGTEXT;
-- Declare cursors
DECLARE tblCursor CURSOR FOR
SELECT t.table_name
FROM INFORMATION_SCHEMA.TABLES t
WHERE t.TABLE_SCHEMA = schemaName
AND t.TABLE_NAME NOT LIKE 'audit_%';
DECLARE colCursor CURSOR FOR
SELECT column_name, column_type
FROM information_schema.columns
WHERE table_schema = schemaName
AND table_name = tblName;
-- Declare handlers
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET done = TRUE;
-- Open table cursor
OPEN tblCursor;
-- Loop through each table
tbl_loop: LOOP
FETCH tblCursor INTO tblName;
IF done THEN
LEAVE tbl_loop;
END IF;
-- Set the audit table name
SET auditTblName = CONCAT('audit_', tblName);
-- Check if the audit table exists
IF (SELECT COUNT(*)
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = schemaName
AND TABLE_NAME = auditTblName) = 0 THEN
-- Audit table does not exist, skip this table
ITERATE tbl_loop;
END IF;
-- Start generating the trigger SQL
SET triggerSQL = CONCAT('
CREATE TRIGGER ', tblName, '_before_insert
BEFORE INSERT ON ', tblName, '
FOR EACH ROW
BEGIN
INSERT INTO ', auditTblName, ' (');
-- Open column cursor
OPEN colCursor;
-- Loop through columns to generate SQL
col_loop: LOOP
FETCH colCursor INTO colName, colType;
IF done THEN
LEAVE col_loop;
END IF;
-- Add column to trigger SQL
SET triggerSQL = CONCAT(triggerSQL, colName, ', ');
END LOOP;
-- Close column cursor
CLOSE colCursor;
-- Finalize trigger SQL
SET triggerSQL = TRIM(TRAILING ', ' FROM triggerSQL);
SET triggerSQL = CONCAT(triggerSQL, ') VALUES (');
-- Re-open column cursor to append values
OPEN colCursor;
col_loop: LOOP
FETCH colCursor INTO colName, colType;
IF done THEN
LEAVE col_loop;
END IF;
-- Add column values to trigger SQL
SET triggerSQL = CONCAT(triggerSQL, 'NEW.', colName, ', ');
END LOOP;
-- Close column cursor
CLOSE colCursor;
-- Finalize the trigger SQL
SET triggerSQL = TRIM(TRAILING ', ' FROM triggerSQL);
SET triggerSQL = CONCAT(triggerSQL, '); END;');
-- Execute the trigger creation SQL
SET @sql = triggerSQL;
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Reset done flag for the next iteration
SET done = FALSE;
END LOOP;
-- Close table cursor
CLOSE tblCursor;
END //
DELIMITER ;
解决方法1.
使用客户端语言,而不是存储过程语言。这允许您一次性将 CREATE TRIGGER 语句格式化为客户端语言中的字符串,并执行它。如果您这样做,则无需准备和执行。而在存储过程语言中,没有立即执行函数;要编写动态语句,您所能做的就是使用准备和执行。事实上,我经常建议避免在任何任务中使用 MySQL 存储过程语言。
解决方法2.
不要使用触发器,而是使用MySQL 审核日志插件。这可能比使用触发器更通用、更安全、更高效。
MySQL 提供的审计插件仅供企业客户使用,但也有免费的开源实现,例如Percona Audit Log Plugin。