before delete触发器必须用signal中断语句才能阻止删除,仅select报错无效;old指向待删行,用于字段级校验;truncate/drop不触发该触发器,需权限控制兜底。

BEFORE DELETE触发器必须用SIGNAL,不能只抛错
MySQL 的 BEFORE DELETE 触发器里,仅执行 SELECT 1/0 或写日志不会阻止删除;必须用 SIGNAL SQLSTATE '45000' 主动中断语句。否则 MySQL 认为“校验通过”,继续执行删除。
常见错误现象:RAISERROR(SQL Server 写法)或 SELECT 'error' FROM DUAL WHERE 1=0 这类“伪报错”完全无效,DELETE 照常提交。
-
SIGNAL是唯一能终止当前语句并回滚本行操作的机制 - MySQL 5.5+ 支持,无需提前声明
HANDLER,直接写就行 - 错误信息会原样返回给客户端,比如
ERROR 1644 (45000): 禁止删除活跃用户
OLD 关键字才是你的数据校验依据
在 BEFORE DELETE 触发器中,OLD 指向即将被删的那行原始数据,所有字段级判断都靠它。别试图查 deleted 表(那是 SQL Server 的写法),MySQL 里不存在这个临时表。
使用场景:拦截状态为 'active' 的用户、未归档的订单、创建时间不足 7 天的配置项。
- 正确写法:
IF OLD.status = 'active' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '活跃用户不可删除'; END IF; - 避免在触发器里做子查询校验,比如
SELECT COUNT(*) FROM orders WHERE user_id = OLD.id—— 容易拖慢甚至死锁 - 如需关联校验,优先把关键状态冗余到本表(如加
is_deletable TINYINT DEFAULT 1),触发器只读本行字段
单次删多行?触发器里没法直接统计,得换思路
触发器是逐行触发的,OLD 每次只代表一行。所以你不能在触发器里用 SELECT COUNT(*) FROM OLD 判断是否删了 1000 行——语法不合法,MySQL 会报错 Unknown table 'OLD'。
真正能落地的限制方式只有两种:
- 用
SQL_SAFE_UPDATES=1全局限制无条件 DELETE(它会拒绝DELETE FROM users;这种语句) - 在应用层或存储过程中预判影响行数,比如先
SELECT COUNT(*) FROM users WHERE ...,超阈值就拒掉整个请求 - 如果非要在数据库侧卡,只能退一步:对高危表禁用
DELETE权限,改用带审批的软删除字段(如deleted_at DATETIME)
TRUNCATE 和 DROP TABLE 根本不走触发器,别白费劲
这是最容易忽略的盲区:TRUNCATE TABLE users 和 DROP TABLE users 属于 DDL 操作,MySQL 的触发器机制完全不监听它们。你在 users 表上建了十个 BEFORE DELETE 触发器,也拦不住一条 TRUNCATE。
必须搭配权限控制才有效:
REVOKE DROP, TRUNCATE ON mydb.users FROM 'dev'@'%';-
REVOKE FILE, PROCESS, SUPER ON *.* FROM 'dev'@'%';(防止绕过) - 线上账号默认只给
SELECT, INSERT, UPDATE,DELETE 权限按需临时开通并绑定工单号注释
触发器能管住的,只是 DELETE 语句本身;超出这个范围的操作,得靠权限、代理层(如 ProxySQL 规则)和审计日志兜底。











