触发器不能单独保障数据安全,须结合权限控制、应用层校验与触发器兜底;MySQL用SIGNAL、SQL Server用INSTEAD OF、PostgreSQL用RAISE EXCEPTION才能真正拦截删除;TRUNCATE/DROP无法被DML触发器捕获,需靠权限限制和事件触发器。

不能靠“写个触发器就万事大吉”——触发器只对 DELETE 语句生效,TRUNCATE TABLE、DROP TABLE、外键级联删除、备份恢复等操作完全绕过它。真正起作用的是组合策略:权限控制 + 应用层校验 + 触发器兜底。
MySQL 的 BEFORE DELETE 必须用 SIGNAL 才能中断删除
常见错误是只写 IF OLD.role = 'admin' THEN SELECT 1/0; END IF; —— 这类操作会执行,但不阻止删除。MySQL 不认为这是“失败”,只是“顺便干了点事”。
-
SIGNAL SQLSTATE '45000'是唯一可靠方式,状态码必须是 5 位字符串,'45000'是通用自定义错误标准 - 错误消息要具体,比如
MESSAGE_TEXT = 'Cannot delete system user id=1',方便日志归类和排查 - 别在触发器里声明
HANDLER或套DECLARE CONTINUE HANDLER,纯属冗余,SIGNAL本身就会立即终止语句 - 示例:禁止删
is_protected = 1的行:CREATE TRIGGER tr_block_protected BEFORE DELETE ON users FOR EACH ROW BEGIN IF OLD.is_protected = 1 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Protected record cannot be deleted'; END IF; END;
SQL Server 要用 INSTEAD OF DELETE,不是 AFTER
AFTER DELETE 触发器运行时数据已删完,抛错也救不回来;INSTEAD OF DELETE 才是真正可拦截的时机,但它不自动执行原操作,你得自己决定“删还是不删”。
- 空触发器体(
BEGIN END)就能阻止所有DELETE,因为没写DELETE FROM ...语句 - 若需放行部分数据,必须显式
DELETE并关联deleted表,例如:DELETE u FROM users u INNER JOIN deleted d ON u.id = d.id WHERE d.role != 'admin' - 严禁在触发器内再写
DELETE FROM users,会递归触发自身,SQL Server 报错Maximum stored procedure, function, trigger, or view nesting level exceeded (limit 32) - 注意:含
ON DELETE CASCADE外键的表不支持建INSTEAD OF DELETE触发器,建表时就会报错Msg 3724
PostgreSQL 需 RAISE EXCEPTION 且 RETURN NULL 不够
只写 RAISE NOTICE 或函数末尾 RETURN NULL 不会阻止删除——前者只是打日志,后者在触发器中甚至无意义。
- 必须用
RAISE EXCEPTION,它会让整个事务回滚,不是单条语句失败 - 批量
DELETE中只要一行触发异常,整条语句全部回滚,其余行也删不掉 - PG 12+ 支持
WHEN子句简化条件判断,例如:CREATE TRIGGER ... BEFORE DELETE ON config WHEN (OLD.is_enabled) EXECUTE FUNCTION check_active_config(); - 避免在触发器函数里查大表,高并发下易锁表;优先把关键标识(如
is_deletable)冗余到本行,直接读OLD.is_deletable
TRUNCATE 和 DROP 完全不触发任何 DML 触发器
这是最容易被忽略的盲区:你在 users 表上建了十个 BEFORE DELETE 触发器,也拦不住一条 TRUNCATE TABLE users 或 DROP TABLE users。
-
TRUNCATE是 DDL 操作,不走行级触发器,不记 binlog 的DELETE事件,不可回滚(除非在事务中) - MySQL 没有机制拦截
TRUNCATE或DROP,只能靠REVOKE DROP, CREATE ON db.* FROM 'user' - PostgreSQL 可用
EVENT TRIGGER拦截TRUNCATE(9.3+)或ddl_command_start拦截DROP TABLE,但需单独创建,不复用 DML 触发器逻辑 - 真正兜底靠权限:收回普通账号的
DELETE权限,改用软删除字段(如deleted_at),把物理删变成带审批的UPDATE
触发器是最后一道脆弱防线,它防不住权限足够高的账号、防不住直接拷贝 .ibd 文件、防不住从备份恢复前的窗口期。最有效的防护永远是:权限最小化 + 应用层预检 + 全局 sql_safe_updates=ON + 通用查询日志告警。

















