只能用 before update 触发器+显式报错才能真正拦截无 where 的 update;after update 因数据已落盘且回滚仅限自身逻辑,无法撤销原更新。

只能用 BEFORE UPDATE 触发器 + 显式报错,才能真正拦住无 WHERE 的 UPDATE;AFTER 触发器完全无效,因为数据早就写进去了。
为什么 AFTER UPDATE 根本拦不住无 WHERE 更新
AFTER UPDATE 在语句执行完成、数据已落盘后才触发。哪怕你在里面写 ROLLBACK 或 SIGNAL,也只是回滚触发器自己的逻辑,原 UPDATE dim_region SET name = 'xxx' 早已生效。常见错误现象是:日志里看到报错,但表里所有行的 name 都被改了——这就是 AFTER 的典型失效场景。
必须用 BEFORE 类型,它在写入前最后一刻介入,才有机会中断整个事务。
PostgreSQL / SQLite 怎么写有效的拦截逻辑
核心是判断是否为“无条件更新”:当触发器中 OLD.id IS NULL(PostgreSQL)或 NEW.id IS NULL(SQLite)且触发类型为 ROW 级时,基本可认定为未命中任何行(即没加 WHERE),但更稳妥的是结合 tg_op 和 tg_level 判断上下文:
- PostgreSQL 示例:
RAISE EXCEPTION 'UPDATE without WHERE is forbidden on dim_region'放在BEFORE UPDATE ON dim_region FOR EACH ROW中 - SQLite 注意:必须先执行
PRAGMA recursive_triggers = ON,否则RAISE(ABORT, ...)不生效 - 两者都支持在触发器函数里用
CURRENT_USER或application_name()做来源识别,但别依赖CURRENT_USER当操作人——它只是连接账号
MySQL 的 SIGNAL 写法和关键限制
MySQL 不支持 RAISE,得用 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'locked'。但有三个硬约束必须满足:
- 必须是
BEFORE UPDATE,不能是 AFTER - 触发器里禁止读写同一张表(比如在
BEFORE UPDATE ON users里再查SELECT COUNT(*) FROM users),会引发死锁或权限错误 - 无法自动识别“无 WHERE”,需靠外部手段辅助判断:例如要求所有合法更新必须带注释
/* safe-update */,然后在触发器里用INFORMATION_SCHEMA.PROCESSLIST(需开启)或会话变量@_app_context检查
真正难的不是写触发器,而是让它不被绕过
触发器本身没有防御纵深:运维用 root@localhost 直连、ETL 工具用高权限账号、或者 MySQL 启动时加了 --skip-grant-tables,都会让触发器彻底失效。生产环境必须闭环控制:
- 普通应用账号只给
SELECT, INSERT,UPDATE 权限仅授予特定存储过程 - 维度表上禁用
ALTER TABLE ... DISABLE TRIGGER,靠收回ALTER权限实现 - 所有 DDL 变更走审批流程,配合 DDL 触发器审计
ALTER_TABLE事件
最易被忽略的一点:批量导入(如 LOAD DATA INFILE 或 INSERT ... SELECT)会让触发器逐行执行,性能暴跌且可能超时。这类操作应单独白名单放行,而非依赖同一套拦截逻辑。










