before delete触发器必须用signal才能真正阻止删除,select 1/0等操作无效;校验须基于old行,禁用子查询;truncate/drop不触发触发器;软删除优先,触发器仅为脆弱防线。

BEFORE DELETE触发器必须用SIGNAL才能真正阻止删除
不抛异常,删操作照常提交。MySQL 的 BEFORE DELETE 触发器里写 SELECT 1/0、INSERT INTO log_table 或 SET @msg = 'blocked' 都无效——这些只是“执行了点东西”,但 MySQL 仍认为校验通过,继续删数据。
唯一能中止当前语句并回滚该行操作的方式是:SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '禁止删除系统账户';
-
SQLSTATE '45000'是通用未定义错误码,MySQL 5.5+ 原生支持,兼容性好 - 错误信息会原样返回客户端,比如报错
ERROR 1644 (45000): 禁止删除活跃用户 - 不能用
RAISERROR(SQL Server 写法)或空SELECT伪报错,语法合法但逻辑失效
如何精准识别“关键数据”并校验OLD行
触发器逐行触发,OLD 就是你唯一能读取的待删数据快照。所有判断必须基于它,不能查表、不能调函数、不能假设上下文。
- 推荐方式:用预设字段标记,如
OLD.is_protected = 1、OLD.role IN ('admin', 'system')、OLD.id IN (1, 100, 999) - 避免子查询:像
SELECT COUNT(*) FROM orders WHERE user_id = OLD.id容易引发死锁或延迟抖动 - 软删场景需额外判断:例如
IF OLD.deleted_at IS NOT NULL THEN SIGNAL ... END IF;,防止硬删已软删记录 - 多条件组合直接用
IF嵌套或OR连接,别拆成多个触发器
TRUNCATE 和 DROP 完全不走触发器,这点最容易被忽略
你在 users 表上建了十个 BEFORE DELETE 触发器,也拦不住一条 TRUNCATE TABLE users 或 DROP TABLE users。它们是 DDL 操作,触发器机制不监听。
-
TRUNCATE不走触发器、不记 binlog 的 DELETE 事件、不可回滚 - 真正兜底靠权限控制:对核心表
REVOKE DELETE ON prod_db.users FROM 'dev_user'@'%'; - 高危操作必须走工单审批,DBA 账号应启用二次认证,禁用
sql_log_bin = 0等绕过手段 - 如果业务允许,优先用软删除字段(如
is_deleted TINYINT DEFAULT 0),把物理删变成 UPDATE
触发器只是最后一道脆弱防线,别把它当主力防护
它防不住权限足够高的账号、防不住 TRUNCATE、防不住备份恢复前的窗口期,也防不住应用层直连后绕过触发器逻辑。
- 更可靠的事前控制:全局启用
sql_safe_updates=ON,让DELETE FROM users;直接报错 - 更可控的流程控制:所有变更必须带影响行数预估,超阈值自动拒掉请求
- 真正可逆的保障:开启
binlog_format=ROW,保留 7 天以上 binlog,并定期做恢复演练 - 复杂规则(比如“删除前检查关联订单总金额 > 10 万”)建议移到应用层或存储过程,别塞进触发器











