不能在mysql触发器中可靠实现软删除数据的自动归档,因为after update触发器无法区分首次软删与重复更新,且禁止子查询、跨库操作失败;before delete不适用软删除场景;唯一安全做法是触发器仅写轻量归档队列,由外部异步任务处理。

不能在MySQL触发器中可靠实现软删除数据的自动归档。
为什么AFTER UPDATE触发器无法识别真实软删除
软删除本质是UPDATE操作,比如UPDATE orders SET is_deleted = 1 WHERE id = 123。虽然可以用AFTER UPDATE触发器监听,但业务中常有批量更新:UPDATE orders SET status = 'closed', is_deleted = 1, updated_at = NOW()。此时仅靠OLD.is_deleted = 0 AND NEW.is_deleted = 1判断,会把所有含is_deleted = 1的更新都当成归档信号,导致误归档。
更严重的是:
- 无法区分“本次更新是否首次软删”——若该行此前已被软删又恢复,再次设为
1就不该重复归档 - MySQL不允许在触发器里对原表做子查询(如
SELECT is_deleted FROM orders WHERE id = NEW.id),否则报ERROR 1442或死锁 - 跨库归档(如写入
archive_db.orders)在触发器内直接失败,无绕过方式
BEFORE DELETE触发器也不适用于软删除场景
软删除不走DELETE,所以BEFORE DELETE根本不会被调用。强行把软删除改成物理删除再归档,就违背了“软删除”的设计初衷——保留数据可恢复性。
即使你改用物理删除配合触发器归档,也要注意:
-
AFTER DELETE中OLD字段可能不完整(尤其含TEXT、JSON或启用了innodb_strict_mode时),部分字段返回NULL - 必须改用
BEFORE DELETE才能确保OLD完整可用,但这就又回到“这不是软删除”的问题
触发器唯一能安全做的,只是轻量标记
如果你坚持要用触发器参与归档流程,它唯一合理角色是快速落一条轻量日志,把归档任务“排队”出去,而不是执行归档本身。
推荐做法:
- 建一张极简的
archive_queue表:id、table_name、pk_value、archived_at、processed(TINYINT) - 在业务层执行软删除时,**额外执行**:
INSERT INTO archive_queue (table_name, pk_value, archived_at) VALUES ('orders', 123, NOW()) - 不用触发器自动插——除非你能100%保证所有软删除路径都走同一段SQL,否则极易漏掉
- 后续由Python脚本或MySQL
EVENT定时扫描archive_queue WHERE processed = 0,查源表快照、校验is_deleted = 1仍成立、写入归档库、再更新processed = 1
真正容易被忽略的点是:归档动作一旦放进触发器,就和主事务强绑定——哪怕只多花50ms,也会拖慢所有写请求;而外部异步任务失败可重试、可限流、可监控,主表完全不受影响。











