不能用BEFORE UPDATE触发器拦截DELETE,因DELETE不触发它;真正可控的是UPDATE is_deleted=1的行为,必须配合禁用DELETE权限与BEFORE DELETE报错双重防护。

不能用 BEFORE UPDATE 触发器“拦截物理删除”——DELETE 语句根本不会触发它。 你真正能控制的,是用户执行 UPDATE 时把 is_deleted 设为 1 的行为。物理 DELETE 必须靠权限管控 + BEFORE DELETE 显式报错双保险。
BEFORE UPDATE 触发器只管 UPDATE,不管 DELETE
MySQL 的 BEFORE UPDATE 触发器对 DELETE FROM users WHERE id = 123 完全无感知。它只在 UPDATE users SET is_deleted = 1 WHERE id = 123 这类语句执行前运行。指望它“拦截 DELETE”是方向性错误。
- 触发器无法改变 SQL 语句类型:解析阶段已确定是
UPDATE还是DELETE,触发器只能响应,不能转换 - 若业务允许的唯一软删方式是改
is_deleted,那BEFORE UPDATE就是你加固这条路径的唯一入口 - 但必须同步禁用
DELETE权限(REVOKE DELETE ON users FROM 'app_user'@'%'),否则所有触发器逻辑形同虚设
在 BEFORE UPDATE 中做三件事:补时间、锁状态、阻误改
典型逻辑是:当 is_deleted 从 0 变成 1,自动记下删除时间;当发现记录已是已删状态,禁止任何字段变更。
- 用
IF OLD.is_deleted = 0 AND NEW.is_deleted = 1 THEN SET NEW.deleted_at = NOW();补审计字段 - 用
IF OLD.is_deleted = 1 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Read-only for deleted records';阻止后续修改 - 避免在触发器里查
users表本身(比如SELECT COUNT(*) FROM users WHERE ...),会触发Can't update table 'users' in stored function/trigger错误
BEFORE DELETE 触发器必须存在,且只做一件事:报错
即使你禁用了 DELETE 权限,DBA 或高权限账号仍可能绕过。所以 BEFORE DELETE 是最后一道防线,它不校验、不日志、不写表,只抛错。
- 必须用
SIGNAL SQLSTATE '45000',不能用RETURN或ROLLBACK(MySQL 不支持) - 错误信息必须是字面量字符串,不能拼接变量:
SET MESSAGE_TEXT = 'Direct DELETE is prohibited',否则静默失败 - 不要在其中查关联表(如订单表)做业务校验——高并发下易锁表或超时;这类检查应前置到应用层或用外键约束兜底
容易被忽略的权限与隔离细节
逻辑删除看似只是字段变更,但落地时权限和事务隔离稍有疏忽,就会出现“删了还能查到”或“删着删着报死锁”。
- 应用连接账号必须
REVOKE DELETE,仅保留SELECT, INSERT, UPDATE—— 这步常被跳过 - 所有查询必须默认加
WHERE is_deleted = 0,不能依赖触发器“自动过滤”,那是行级安全(RLS)的事,MySQL 原生不支持 -
BEFORE UPDATE中对NEW字段的修改,在READ COMMITTED隔离级别下对其他并发事务不可见,直到整个事务提交

















