关键配置字段必须用before update触发器硬拦截,因其可防御运维sql、旧服务直连、sql注入等绕过应用校验的情况;触发器仅校验禁止修改字段,不查表、不调函数,且需配合delete/insert触发器及权限管控。

不能靠触发器禁止“被触发器修改”——触发器本身是修改的发起者,不是被保护对象。真正要防的是人为或程序对表的非法 DML 操作,保护手段必须落在触发器自身设计、权限控制和执行路径上。
BEFORE UPDATE 触发器必须只校验字段,不查表不调函数
关键配置表(如 system_config)的 pay_timeout 字段一旦被改错,可能引发资损。此时 BEFORE UPDATE 触发器是唯一能拦截运维直连、旧服务绕过 ORM、甚至 SQL 注入的物理防线。
- 只写明确禁止修改的字段判断,例如:
IF OLD.pay_timeout != NEW.pay_timeout THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'pay_timeout is read-only'; END IF; - 绝对避免在触发器里执行
SELECT查询其他表、调用自定义函数,或做 JSON 解析——MySQL 8.0 前不支持触发器内事务,查表会拖慢所有UPDATE,高并发下还容易锁表 - 不要用
NEW.field = OLD.field覆盖值来“静默保护”,这会让应用误以为更新成功,掩盖真实问题
DELETE/INSERT 也得拦,光拦 UPDATE 不够
一个只设 BEFORE UPDATE 的触发器,对 DELETE FROM system_config WHERE id = 1 或 INSERT INTO system_config (...) VALUES (...) 完全无效。业务若要求配置行不可增删,必须补上另外两个触发器:
-
BEFORE DELETE ON system_config:直接SIGNAL拒绝删除 -
BEFORE INSERT ON system_config:同样用SIGNAL拦新增,除非你允许初始化导入 - 注意:MySQL 不允许在触发器里对本表做
INSERT/UPDATE/DELETE,否则报错ERROR 1442
权限必须收走,否则触发器形同虚设
如果给应用账号或运维账号直接授予了 UPDATE 权限,那触发器只是最后一道“补漏”,而不是防线主体。真要防住,就得从权限源头卡死:
- 用
REVOKE UPDATE, DELETE, INSERT ON system_config FROM 'app_user'@'%';收掉直接 DML 权限 - 只开放一个
SECURITY DEFINER函数(如update_config_safe()),由它内部做白名单字段校验 + 写变更日志 - 确认 DBA 已授权
TRIGGER权限——线上环境常默认关闭,建触发器前不确认会失败 - 别忘了
CURRENT_USER()和USER()区别:前者是授权用户,后者是连接用户;审计时建议用CURRENT_USER()
日志记录必须轻量,别在触发器里写重操作
生产环境要求留痕,但常见错误是让触发器直接往日志表 INSERT 完整 JSON 差异或关联查询结果,这极易引发死锁或长事务阻塞。
- 触发器里只记最简四元组:
table_name、pk_id、old_value、new_value,且字段类型用VARCHAR或TEXT避免隐式转换 - 不要用
JSON_OBJECT()拼差异——MySQL 5.7 不支持,8.0+ 会显著拉长行锁持有时间 - 更稳妥的做法是触发器只写一条轻量记录(比如仅存
config_id和updated_by),由应用层异步补全详情
最容易被忽略的是:触发器无法防御 DROP TABLE 或 ALTER TABLE,这类 DDL 操作需靠更高层权限隔离(如只给 SELECT + EXECUTE 权限,禁用 ALTER)、数据库审计日志或备份策略兜底。











