备份必须用 BEFORE UPDATE/DELETE 触发器配合 OLD 取值写入结构严格一致的备份表;审计日志应使用 AFTER 触发器记录操作类型和字段级差异;DELETE 必须用 BEFORE 阶段确保关联数据可查,且大表禁用行级触发器归档。

不能直接“备份整行”,只能用 BEFORE UPDATE/DELETE + OLD 取值写入结构严格一致的备份表;想存变更日志(审计用),就该用 AFTER + 记操作类型和字段级差异。
BEFORE UPDATE 里怎么安全取旧值
只有 BEFORE UPDATE 和 BEFORE DELETE 能用 OLD,它指向修改/删除前的那行数据。别在触发器里写 SELECT * FROM orders WHERE id = NEW.id——MySQL 会报 ERROR 1442,因为正在操作的表不能被反查。
- 备份表(如
orders_backup)字段顺序、类型、NULL 属性必须和原表完全一致,否则INSERT INTO ... VALUES (OLD.id, ...)会因列数不匹配失败 - 原表主键是自增的,备份表对应字段不能设
AUTO_INCREMENT,否则插入OLD.id会冲突 - 如果只备份部分字段(比如排除
updated_at这类噪音时间戳),必须显式列出字段名:INSERT INTO orders_backup (id, user_id, amount) VALUES (OLD.id, OLD.user_id, OLD.amount)
为什么 DELETE 必须用 BEFORE 而不是 AFTER
AFTER DELETE 里 OLD 虽然还能访问,但原行已物理删除。一旦你要关联查其他表补信息(比如把用户昵称一起记进备份),AFTER 阶段可能因事务隔离或级联删除导致查不到数据。
- 正确写法是
BEFORE DELETE+JOIN users u ON u.id = OLD.user_id,此时关联数据一定存在 - 但要注意:如果
users.id没索引,这个 JOIN 会让每次DELETE变慢,线上表务必加索引 -
BEFORE DELETE中不能改OLD的值,它只读;而BEFORE UPDATE可以改NEW,实现自动填充或校验
大表上批量删除时触发器会拖垮数据库
单次 DELETE FROM logs WHERE created_at < '2025-01-01' 如果影响 50 万行,触发器就会同步执行 50 万次 INSERT,锁等待、I/O、事务膨胀全来了。
- 禁止在日均删除超 10 万行的表上用行级
BEFORE DELETE归档 - 真实场景应改用定时任务分批处理:
INSERT INTO logs_archive SELECT * FROM logs WHERE id IN (SELECT id FROM logs WHERE created_at < '2025-01-01' LIMIT 1000); DELETE FROM logs WHERE id IN (...); - 归档表的
archived_at字段必须建索引,否则按时间范围查“上周删了哪些”就是全表扫描
别把触发器当通用备份工具
触发器只负责“记录动作”,不是备份系统。它不保存事务快照,不防误操作回滚,更不替代 mysqldump 或 binlog。一个典型误区是:以为写了 BEFORE UPDATE 就能靠它一键还原整张表——实际你得自己拼 SQL 回滚,且无法保证中间状态一致性。
最容易被忽略的是字段兼容性:比如原表加了个 JSON 字段,备份表没同步改类型,后续插入就隐式截断;或者应用层用了 utf8mb4,备份表还是 utf8,emoji 就存乱码。这些细节不盯紧,备份表早晚会变成“假备份”。


















