触发器的时机哲学:为什么BEFORE和AFTER不是“先后顺序”,而是数据主权的交接仪式
你有没有遇到过这样的场景:
- 一个AFTER INSERT触发器调用外部HTTP接口,结果整个订单事务卡住3秒,下游服务超时雪崩;
-BEFORE UPDATE里写了SET NEW.updated_at = NOW(),但日志里发现某些记录的更新时间比实际操作晚了200ms;
- 更隐蔽的是——明明加了UNIQUE(email)约束,却在高并发注册时收到两条重复邮箱的报警,而数据库里只存了一条。
这些都不是Bug,而是你和数据库之间,对“数据何时真正属于它”这件事,还没达成共识。
数据库从不“被动存储”:它一直在等一个交接仪式
很多人把触发器当成“事件监听器”——像前端的onClick一样,点一下就执行一段逻辑。但数据库不是浏览器,它的执行模型根植于ACID与MVCC。BEFORE和AFTER的本质,不是“谁先谁后”的时间排序,而是数据库在事务生命周期中两次郑重其事的交接仪式:
BEFORE:是数据库向应用伸出手,说:“你给我的数据,我还没收下。现在,你还有最后一次修改它的机会。”AFTER:是数据库完成所有校验、落盘、索引更新后,轻轻点头:“这行数据,现在正式归我管了。你想基于它的终态做点什么,我允许。”
这个交接点,决定了你能做什么、不能做什么、以及做了之后会引发什么连锁反应。
BEFORE:不是“前置钩子”,而是“数据守门员”的上岗时刻
它能做的三件事,件件都踩在事务最敏感的神经上
| 能力 | 为什么只能在这里做 | 真实工程代价(若错放) |
|---|---|---|
改NEW值(如NEW.email = LOWER(NEW.email)) | NEW伪记录只在此刻可写;AFTER中NEW已固化为只读快照 | 若放到AFTER,字段值已写入,再改只是改内存副本,对表无影响 |
抛异常拦截(如余额为负则SIGNAL) | 此时尚未生成undo log、未更新索引、未加锁扩散,回滚成本趋近于零 | 若在AFTER中校验失败再 |