mysql表级审计最可控方式是用after insert/update/delete触发器写入audit_log表,需手动为每张业务表创建触发器;audit_log应含table_name、record_id、action_type、old_data/new_data等字段,并建立合理索引。

直接上结论:用 AFTER INSERT、AFTER UPDATE、AFTER DELETE 触发器写入自定义 audit_log 表,是 MySQL 表级审计最可控、最易落地的方式;它不依赖插件、不改配置、不重启服务,但必须手动为每张业务表创建对应触发器。
AFTER 触发器比 BEFORE 更适合审计
-
AFTER触发器在 DML 操作已成功提交(或回滚)后执行,能确保记录的是最终生效的数据状态 -
BEFORE触发器中NEW可被修改,OLD不能被修改,但若业务逻辑里有异常或事务失败,BEFORE写入的审计日志可能“记录了没发生的操作” - 审计的核心诉求是“事实发生了什么”,不是“意图做什么”,所以优先选
AFTER - 注意:
AFTER触发器无法阻止主操作(比如不能像BEFORE那样用SIGNAL拒绝插入),但这对审计反而是优点——避免审计逻辑干扰业务一致性
audit_log 表设计要兼顾通用性与可查性
- 必须支持多表复用,不能为每个业务表建一张审计表
- 推荐字段:
-
table_name VARCHAR(64):记录被审计的表名,如'users' -
record_id VARCHAR(255):主键值,统一转字符串(兼容BIGINT、UUID、COMPOSITE PK) -
action_type ENUM('INSERT','UPDATE','DELETE'):明确操作类型 -
old_data JSON和new_data JSON:只存变更涉及字段,非全量;UPDATE 时两者都填,INSERT 只填new_data,DELETE 只填old_data -
changed_by VARCHAR(255) DEFAULT (CURRENT_USER()):用CURRENT_USER()而非USER(),前者是授权用户,后者可能是代理连接用户,更可靠 -
change_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP:用CURRENT_TIMESTAMP,不是NOW(),避免时区歧义
-
- 索引建议:
CREATE INDEX idx_audit_table_time ON audit_log (table_name, change_timestamp);CREATE INDEX idx_audit_record ON audit_log (table_name, record_id);
为业务表写触发器时,字段映射最容易出错
- 错误现象:INSERT 后
audit_log中new_data字段为空,或 JSON 格式非法 - 原因常是:
- 直接拼接字符串构造 JSON,比如
CONCAT('{"name":"', NEW.name, '"}')—— 一旦NEW.name含单引号或反斜杠就崩 - 漏写
DELIMITER $$,导致分号被 MySQL 当作语句结束,触发器体被截断 - 对
NULL字段未做处理,JSON_OBJECT('col', NULL)会把整个 key 丢掉,看不出该字段是否真为 NULL
- 直接拼接字符串构造 JSON,比如
- 正确写法示例(以
users表为例):DELIMITER $$ CREATE TRIGGER users_after_insert AFTER INSERT ON users FOR EACH ROW BEGIN INSERT INTO audit_log (table_name, record_id, action_type, new_data, changed_by) VALUES ('users', CAST(NEW.id AS CHAR), 'INSERT', JSON_OBJECT('id', NEW.id, 'username', NEW.username, 'email', NEW.email), CURRENT_USER()); END$$ DELIMITER ; - UPDATE 触发器中,务必显式列出所有需审计的字段,不要用
*或动态列名 —— MySQL 触发器不支持元数据查询
性能和维护上几个硬约束不能绕开
- 单个触发器体建议控制在 20 行以内,避免嵌套
IF、循环或调用存储过程 —— 触发器执行在事务内,慢 = 锁等待时间长 = 业务写入卡顿 - 不要在触发器里写跨库 INSERT、调用外部 HTTP、或访问大表 JOIN —— 这些操作失败会导致主事务失败
- 每加一张新业务表,就得补 3 个触发器;建议用脚本生成(例如 Python 模板渲染 SQL),别手敲
-
audit_log表本身要定期归档(比如按月分区),否则几年后单表超千万行,SELECT查某条记录会越来越慢
真正难的不是写第一个触发器,而是让这套机制在 50 张表、3 年运行、每天百万级变更下不出错 —— 关键在于克制:只记必要字段、只用 AFTER、不碰事务外逻辑、索引跟上、归档计划写进运维排期。











