mysql触发器无法获取真实操作人,需应用层通过set会话变量传入@current_user_id等标识,并在触发器中用ifnull兜底;日志表必须用innodb引擎+ after触发器保证事务一致性,且字段须显式列出、索引要精简。

MySQL触发器本身无法直接拿到真实业务操作人,SESSION_USER()、CURRENT_USER()、USER() 全部返回的是数据库连接账户,不是张三李四——应用用连接池统一以 app@10.0.0.5 连接,日志里就全是这个值。
必须由应用层显式传入操作人标识
触发器不带上下文,只能靠应用在每次 DML 前主动设会话变量。这步漏掉,@current_user_id 就是 NULL,后续日志全崩。
- 应用执行 SQL 前,先发一条:
SET @current_user_id = 'u_12345'; SET @client_ip = '192.168.1.100'; - 触发器里直接用:
NEW.audit_user_id := IFNULL(@current_user_id, 'unknown');(注意:不能写SELECT @current_user_id,触发器内禁止查询) - 变量作用域仅限当前连接,连接池场景下必须每条业务 SQL 前重设,否则可能残留上个用户的值
- DBA 直连或脚本执行时容易漏设,所以
IFNULL(..., 'unknown')是硬性兜底,不能省
AFTER 触发器 + InnoDB 日志表是唯一安全组合
用 BEFORE 记操作时间会错乱,主表插入失败时日志却已落盘;用 MyISAM 日志表更危险——它不参与事务,主表回滚了,日志还在。
- 一律用
AFTER INSERT/UPDATE/DELETE,确保日志与主表变更原子性绑定 - 日志表必须声明
ENGINE=InnoDB,否则事务隔离失效 - 禁止在触发器里对触发表做任何
SELECT,比如INSERT INTO log SELECT OLD.* FROM users会报ERROR 1442 - 所有字段必须显式列出:
INSERT INTO audit_log (table_name, action, user_id, old_name, new_name) VALUES ('users', 'UPDATE', OLD.id, OLD.name, NEW.name)
日志表设计不当会让主业务慢三倍
索引不是越多越好。给每个字段都加单列索引,等于让每次 INSERT 同步刷三次磁盘。
- 只建一个复合索引:
INDEX idx_table_action_time (table_name, action, created_at) - 避免在日志表存大字段,
JSON快照类数据压缩后存MEDIUMTEXT并关闭索引 - 定期归档别用
DELETE FROM audit_log WHERE created_at ,锁表太久;改用 <code>RENAME TABLE audit_log TO audit_log_2025q1+ 新建空表 - 高并发写入场景下,日志表建议单独库、独立磁盘,和业务库物理隔离
最常被忽略的一点:所有绕过 SQL 层的修改——比如直接 mysqldump 导入、LOAD DATA INFILE、中间件批量写入、甚至 MySQL Router 透传——触发器一概捕获不到。真要全覆盖审计,得在应用网关或代理层埋点,而不是只依赖触发器。











