核心是利用before update触发器在更新前校验new.balance是否合法,如禁止负数、限制变动范围等;after update无法阻止非法值写入,因更新已完成。

触发器里怎么判断 NEW.balance 是否合法
核心是利用 BEFORE UPDATE 触发器,在更新前检查 NEW.balance 是否符合业务规则。不能等更新完成再查,否则非法值已写入。常见校验逻辑包括:不允许负数、变动幅度不能超阈值、必须为整数(如游戏金币)、或需满足“变动值 = 某个明细表 sum”等前置条件。
示例场景:用户余额只允许通过充值(+)和扣费(−)操作变更,且单次变动不得低于 −1000 或高于 +5000:
DELIMITER $$ CREATE TRIGGER check_balance_update BEFORE UPDATE ON users FOR EACH ROW BEGIN IF NEW.balance 5000 OR NEW.balance - OLD.balance <h3>为什么不能在 AFTER UPDATE 里做校验</h3><p><code>AFTER UPDATE</code> 触发器无法阻止非法值写入——它在更新完成后才执行,此时 <code>NEW.balance</code> 已落库。即使你在里面抛错,事务已提交,回滚不了(除非显式加 <code>START TRANSACTION</code> 并手动 ROLLBACK,但触发器本身不支持)。所以所有防御性校验必须放在 <code>BEFORE</code> 阶段。</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill2334" title="MySQL"><img src="https://img.php.cn/upload/skill/000/000/081/178900927846657.jpg" alt="MySQL" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill2334" title="MySQL" class="overflowclass">MySQL</a> <p class="overflowclass">编写正确的MySQL查询,避免字符集、索引和锁方面的常见陷阱。</p> </div> <a rel="nofollow" href="/xiazai/skill2334" title="MySQL" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>
-
BEFORE UPDATE中修改NEW.balance是允许的(比如自动截断小数),但不推荐用于“修复”非法值,应直接报错更清晰 - 若业务允许“自动归正”,也必须确保逻辑幂等,例如
SET NEW.balance = GREATEST(0, NEW.balance) - 注意:触发器中不能调用含事务控制或外部连接的存储函数,否则可能报错
Can't update table 'xxx' in stored function/trigger
触发器对并发更新的安全性影响
触发器本身不提供额外锁机制,NEW 和 OLD 值基于当前行锁(record lock)下的快照。如果两个事务同时更新同一行,MySQL 会按加锁顺序串行执行触发器,所以校验逻辑不会因并发而失效。但要注意:
- 触发器内不要查其他表做复杂校验(如 SELECT FROM orders WHERE user_id = NEW.id),否则可能引发死锁或性能抖动
- 避免在触发器里调用
SLEEP()、循环等待或远程 API,这会阻塞整个 UPDATE 语句 - 若校验依赖另一张表的聚合结果(如“余额不能超过授信额度”),建议把额度缓存在
users.credit_limit字段,而非实时 JOIN 查询
测试时容易忽略的边界情况
光测 UPDATE users SET balance = -100 WHERE id = 1 不够。真实环境中这些情况常被漏掉:
- NULL 值更新:
UPDATE users SET balance = NULL—— 若字段允许 NULL,需显式判断IF NEW.balance IS NULL THEN ... - 浮点精度问题:如果 balance 是
DECIMAL(12,2),但应用传了100.123,MySQL 会自动四舍五入,触发器看到的是截断后的值,校验要基于这个“入库值”而非原始输入 - 触发器未覆盖 INSERT:新增用户时也可能插入非法余额,需另建
BEFORE INSERT触发器,逻辑复用校验片段 - ALTER TABLE 修改字段类型后,原有触发器仍生效,但若类型变化导致隐式转换(如从 INT 改为 BIGINT),校验表达式行为可能偏移
真正难的不是写触发器,而是厘清“谁有权限改余额”“改之前必须查哪些状态”“失败时前端看到什么错误码”。触发器只是最后一道闸门,上游逻辑没约束好,它天天报警。










