mysql触发器只能在before delete中安全捕获并记录被删行,因old伪记录此时完整可用;after delete中行已物理删除,且禁止查原表或用insert...select,须显式插入字段并统一时区、用datetime和now()确保日志准确。

触发器能捕获 DELETE 但不能直接还原被删行
MySQL 触发器在 BEFORE DELETE 或 AFTER DELETE 阶段可以执行逻辑,但注意:OLD 伪记录只在 BEFORE DELETE 中完整可用;进入 AFTER DELETE 后,OLD 仍存在,但对应行已在表中物理删除,无法再查原表反推。所以审计日志必须在 BEFORE DELETE 里把 OLD.* 写入日志表。
-
BEFORE DELETE是唯一能安全读取整行原始值的时机,错过就丢数据 - 日志表字段要和原表严格对齐(含类型、长度),否则插入失败或截断
- 别用
AFTER DELETE去查原表补字段——并发删同一行时可能查不到,或查到其他事务刚插入的脏数据 - 如果原表有
JSON、TEXT、BLOB字段,日志表对应字段也得是同类型,否则触发器报Truncated incorrect DOUBLE value类错误
INSERT INTO audit_log SELECT 在触发器里会报错
MySQL 不允许触发器里对当前正在修改的表做 SELECT(哪怕只是查别的行),更不允许用 INSERT ... SELECT 直接抄原表结构。常见报错是:Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
- 必须显式列出所有字段:用
INSERT INTO audit_log (id, name, created_at, op_type, op_time) VALUES (OLD.id, OLD.name, OLD.created_at, 'DELETE', NOW()); - 不能写
INSERT INTO audit_log SELECT *, 'DELETE', NOW() FROM deleted_table—— 这语法本身就不合法,且违反 MySQL 触发器限制 - 字段多时建议用视图或脚本生成 INSERT 语句,避免手写漏字段导致
Column count doesn't match value count - 如果 audit_log 表有自增主键,记得跳过它,或显式设为
NULL(取决于定义)
时间戳用 NOW() 而不是 CURRENT_TIMESTAMP
两者在大多数场景下结果一样,但在触发器里行为不同:CURRENT_TIMESTAMP 是语句开始时间,而 NOW() 是函数执行时刻。DELETE 语句若含子查询或锁等待,两个函数可能差几毫秒;审计日志要求操作时间尽可能贴近真实删除动作点,所以用 NOW() 更准。
-
NOW()返回触发器实际执行时的时间,受事务隔离级别影响小 -
CURRENT_TIMESTAMP可能在语句解析阶段就固化,尤其在存储过程嵌套调用时偏差更大 - 别依赖系统时区自动转换——确保 MySQL 服务端、连接客户端、audit_log 表字段都用统一时区(如
UTC),否则日志时间乱序 - audit_log 表的 time 字段推荐用
DATETIME(非TIMESTAMP),避免TIMESTAMP自动转时区带来的隐式转换风险
高并发下触发器写日志可能成性能瓶颈
每个 DELETE 都强制同步写一次 audit_log,相当于把单条删操作变成“删 + 插”两步 I/O。当批量删(如 DELETE FROM orders WHERE status = 'canceled')时,触发器会逐行触发,而不是批处理。
- 10 万行删除会触发 10 万次 INSERT,极易打满磁盘 I/O 或锁住 audit_log 表
- audit_log 表必须建好索引:至少在
op_time和table_name上建复合索引,否则查日志慢 - 不建议在生产核心链路用触发器做全量审计;可改用 binlog 解析(如
maxwell、canal)异步采集 - 如果坚持用触发器,audit_log 表引擎必须是
InnoDB(支持事务一致性),别用MyISAM(无事务,崩溃后日志与主表不一致)
OLD 的生命周期边界、字段映射的隐式截断、以及同步写日志带来的放大效应——这些地方一不留神,日志就断、就错、就拖垮业务。











