必须用 BEFORE INSERT 和 BEFORE UPDATE 触发器,因校验需在数据写入前阻止非法值;金额校验应使用数值范围而非正则;抛异常须用 SIGNAL/RAISE 等标准语法并带明确错误信息;批量更新等场景需结合 OLD/NEW 值做相对变化校验。

触发器该放在 UPDATE 还是 BEFORE INSERT?
必须用 BEFORE INSERT 和 BEFORE UPDATE,不能用 AFTER。因为校验失败要阻止写入,AFTER 触发时数据已落库,再抛错也晚了——事务虽可回滚,但日志、主从同步、应用层感知都已发生偏差。
常见错误是只写 UPDATE 触发器,漏掉 INSERT 场景。比如用户直接 INSERT 一条伪造的高金额记录,绕过前端新增表单的限制。
-
BEFORE INSERT拦截非法初始值 -
BEFORE UPDATE拦截后续篡改(如后台脚本、SQL 工具直连) - 两个触发器逻辑应一致,避免校验缺口
金额字段校验该用正则还是数值范围?
用数值比较,别用正则。正则匹配字符串易被绕过(如 '100.00e0'、'+100'),且无法防止科学计数法或超精度浮点表示。数据库层面应信任类型系统:DECIMAL(10,2) 字段就按数值处理。
典型校验逻辑:金额必须 ≥ 0 且 ≤ 99999999.99(根据业务定上限)。注意不要硬写死 99999999.99,建议提取为常量或配置表字段,方便后续调整。
立即学习“前端免费学习笔记(深入)”;
- 检查
NEW.amount 或 <code>NEW.amount > 99999999.99 - 避免用
ROUND(NEW.amount, 2) != NEW.amount校验小数位——DECIMAL 类型本身已保证精度,此判断反而可能因隐式转换出错 - 若业务允许负数(如退款),则下限改为业务规则值,而非简单 0
触发器里抛异常该用 SIGNAL 还是 RAISE?
MySQL 5.5+ 用 SIGNAL SQLSTATE '45000';PostgreSQL 用 RAISE EXCEPTION;SQL Server 用 THROW。语法不通用,迁移时容易翻车。最稳妥的是统一用带明确信息的自定义错误码:
-- MySQL 示例 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Amount out of allowed range: [0, 99999999.99]';
关键点:错误信息必须包含具体范围,方便 DBA 或运维快速定位策略边界,而不是只写 “invalid amount”。另外,别依赖 ERROR_MESSAGE() 在应用层解析——不同驱动对 SIGNAL 的封装程度不一,应以 SQLSTATE 码为准做分类处理。
- SQLSTATE
'45000'是标准未定义异常,兼容性最好 - 避免用
SELECT 1/0等副作用方式“模拟报错”,不可控且难维护 - 测试时务必用命令行直连执行 INSERT/UPDATE,绕过 ORM 缓存和中间件拦截
为什么触发器拦不住某些 UPDATE 场景?
常见漏网之鱼:批量更新(UPDATE ... SET amount = amount * 1.1)、子查询赋值(UPDATE t1 SET amount = (SELECT max(amount) FROM t2))、以及触发器未覆盖的字段别名或计算列。根本原因是触发器只看到 NEW.amount 的最终值,看不到来源逻辑。
所以不能只校验单次赋值结果,还得结合上下文约束。例如:禁止金额变动超过 ±10%(相比原值),或要求必须提供修改理由字段(update_reason 非空)。
- 在
BEFORE UPDATE中对比OLD.amount和NEW.amount - 若业务允许合理浮动,用相对变化率而非绝对阈值
- 注意 NULL 处理:若
OLD.amount为 NULL,NEW.amount却非 NULL,这属于初始化,不应套用浮动规则
真正难防的是有 DBA 权限的人禁用触发器或绕过权限体系直写表。触发器只是最后一道校验防线,不是访问控制替代品。它的价值在于让“误操作”变难,而非让“恶意提权”变不可能。


















