触发器仅用于BEFORE INSERT金额合法性校验,禁止更新或查询余额表;AFTER INSERT才可安全更新余额,且须带WHERE条件;并发超扣与原子性需应用层事务+对账保障。

BEFORE INSERT 里只做金额校验,别碰余额表
触发器不能在 BEFORE INSERT 中更新余额表,否则会触发 ERROR 1442。它适合干一件事:用 SIGNAL SQLSTATE '45000' 拦住明显非法的流水,比如取款金额为正、存款金额为负、金额为 NULL 或零值。
- 检查前先
TRIM(NEW.type),避免空格导致匹配失败 - 对
deposit和withdraw分开判断,不要写成模糊的IF NEW.amount > 0 - 禁止在触发器里查
balance表来判断余额是否充足——这既不准(并发下读到旧值),又报错(SELECT ... FROM balance会冲突)
AFTER INSERT 后同步更新余额,但必须加 WHERE 条件
AFTER INSERT 是唯一能安全更新 balance 表的时机,但更新语句必须带明确 WHERE 条件,且不能依赖子查询查本表——否则仍可能触发 ERROR 1442 或锁表。
- 正确写法:
UPDATE balance SET amount = amount + NEW.amount WHERE account_id = NEW.account_id - 错误写法:
UPDATE balance SET amount = (SELECT SUM(amount) FROM transinfo WHERE account_id = NEW.account_id)—— 全量重算会锁表、慢、且不幂等 - 如果
transinfo有状态字段(如status = 'confirmed'),更新前要加条件过滤,避免未确认流水污染余额
并发超扣问题无法靠触发器解决
两个并发 INSERT 都读到同一余额、都通过校验、都执行 UPDATE balance SET amount = amount - X,结果就是余额被多扣。触发器对此无能为力。
- 真正防超扣只能靠应用层:先
SELECT amount FROM balance WHERE account_id = ? FOR UPDATE,再判断、再UPDATE - 或者用原子语句:
UPDATE balance SET amount = amount - ? WHERE account_id = ? AND amount >= ?,靠影响行数是否为 1 判断是否成功 - 触发器里写
IF NEW.amount > (SELECT amount FROM balance WHERE account_id = NEW.account_id)不仅报错,还毫无意义
余额一致性必须靠“事务 + 对账”双保险
触发器只是窄门,不是保险柜。一笔转账涉及两笔流水(扣A、加B),触发器只能保证单条流水更新余额,无法保证二者原子性。
- 应用层必须用显式事务包裹:BEGIN → INSERT 两条流水 → UPDATE 两个余额 → COMMIT
- 定时对账任务每天跑一次,比对
transinfo累计净额与balance表当前值,发现差异立即告警 - 把触发器设为可开关:在触发器开头加
IF NOT EXISTS (SELECT 1 FROM sys_config WHERE k = 'balance_sync_enabled' AND v = 'on'),出问题时快速禁用
实际生产中,最常被忽略的是:触发器一旦上线,就很难动态修改逻辑,而余额类业务规则经常变(比如新增手续费、冻结额度)。与其把复杂判断塞进触发器,不如让应用层承担主责,触发器只守底线——金额非空、符号合规、账户存在。

















