能,但必须用before delete触发器配合signal,且只对显式delete语句生效;truncate、级联删除、load data等完全绕过。核心是判断old行是否匹配禁止条件后抛出sqlstate'45000'错误中断。

能,但必须用 BEFORE DELETE 触发器配合 SIGNAL,且只对显式 DELETE 语句生效;TRUNCATE、级联删除、LOAD DATA 等完全绕过。
如何用 BEFORE DELETE 拦截指定记录的删除
核心是判断 OLD 行是否匹配禁止条件,再抛出错误中断。不能靠“回滚事务”或“删自己表”这类取巧方式——MySQL 8.0+ 支持标准 SIGNAL,应直接使用。
-
OLD只在DELETE和UPDATE触发器中可用,代表被删/将改的原始行 - 禁止删除 ID 为 1 和 2 的记录:
IF OLD.id IN (1, 2) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Protected record cannot be deleted'; END IF; - 禁止删除状态为
'locked'的用户:IF OLD.status = 'locked' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Locked user is immutable'; END IF; - 避免在触发器里查其他表(如
SELECT ... FROM audit_log),高并发下易锁表或超时
SIGNAL 报错后客户端看到什么
客户端收到的是标准 MySQL 错误码 1644,消息体就是你 SET MESSAGE_TEXT 设的内容。这点很关键:如果用 SELECT 1/0 强制报错,错误信息会是 "Division by zero",运维根本看不出是策略拦截还是真出 bug。
- 必须用
SQLSTATE '45000'—— 这是自定义异常的标准状态码 - 消息文本要具体,比如
'Deletion blocked: record id=42 marked as system-critical',方便日志归类 - 不要依赖
ERROR_MESSAGE()或其他函数捕获——触发器里无法读取上层错误上下文
哪些删除操作根本不会触发这个逻辑
触发器不是防火墙,它只响应 SQL 层的 DELETE 语句。以下所有操作完全不受影响:
-
TRUNCATE TABLE users:不走触发器,也不支持事务回滚 - 外键
ON DELETE CASCADE导致的子表自动删除:父表删一行触发器,子表删不触发(除非你在子表也单独建) -
mysqldump --delete或mysqlpump的底层批量操作:可能绕过语句级校验 - 直接拷贝
.ibd文件、从备份恢复、LOAD DATA INFILE:压根没进 SQL 执行引擎
真正起作用的,永远是权限控制(如收回普通账号的 DELETE 权限)+ 应用层校验(如 ORM 层加 WHERE is_deletable = 1)+ 触发器兜底三者组合。单靠触发器防误删,就像只锁门不关窗。











