BEFORE UPDATE触发器必须用SIGNAL中断,不能靠赋值回滚;正确做法是在BEFORE中判断并SIGNAL SQLSTATE '45000',时间判断需精确到分钟级,命名应表意明确,权限回收须四者齐撤并FLUSH PRIVILEGES。

BEFORE UPDATE触发器必须用SIGNAL中断,不能靠UPDATE回滚
MySQL触发器里没有“取消本次更新”的原生语义。设NEW.pay_timeout = OLD.pay_timeout看似回滚,实则应用层收不到错误,会误以为修改成功。真正生效的只有SIGNAL SQLSTATE '45000'——它强制终止整个语句并抛出可捕获的异常。
常见错误写法:
- 用
SELECT 1/0触发除零错误(不明确、难维护) - 在
AFTER UPDATE里再UPDATE同表(直接死锁) - 只查时间不
SIGNAL(条件判断了但放行)
正确姿势是:在BEFORE UPDATE中做判断,命中即SIGNAL,不给任何执行机会。
时间判断要避开BETWEEN陷阱,尤其跨天和整点边界
BETWEEN 18 AND 21只覆盖18:00、19:00、20:00、21:00这四个整点,漏掉21:01–21:59。非工作时间拦截必须精确到分钟级,否则运维半夜改个配置就成功了。
推荐组合:
- 周一至周五 18:00–06:00 禁止:用
(WEEKDAY(NOW()) BETWEEN 0 AND 4) AND (HOUR(NOW()) >= 18 OR HOUR(NOW()) - 严格到分钟(如禁止 09:00–17:59):加
OR (HOUR(NOW()) = 17 AND MINUTE(NOW()) >= 59)冗余但显式 - 服务器时区≠业务时区?必须用
CONVERT_TZ(NOW(), '+00:00', '+08:00')转换后再提取
触发器名和权限需匹配真实运维场景
别起tr_config_block这种泛化名。生产环境建议按表+动作+时段命名,比如trg_before_update_system_config_offhours,方便DBA快速定位。
权限方面,仅撤销SYSTEM_VARIABLES_ADMIN不够——CONNECTION_ADMIN隐式允许SET GLOBAL,SESSION_VARIABLES_ADMIN支持SET PERSIST写入配置文件。必须四者一次性回收:
REVOKE SYSTEM_VARIABLES_ADMIN, SESSION_VARIABLES_ADMIN, CONNECTION_ADMIN, SUPER ON *.* FROM 'dba'@'10.20.30.%';
然后立刻执行FLUSH PRIVILEGES;,否则缓存不刷新,权限还在生效。
测试时最容易漏掉的三个点
一是跨天逻辑没覆盖23:59 → 00:00过渡;二是用SHOW GRANTS查权限,看不到角色继承的权限,得查information_schema.role_table_grants;三是忘了DELIMITER $$没改回来,导致后续SQL全报错。
最危险的是:触发器只拦DML,不拦LOAD DATA INFILE或直连UPDATE脚本——关键配置表还得配合最小权限账号+审计日志双保险。


















