核心是用BEFORE触发器中IF子查询判断NEW.quantity是否大于(SELECT stock FROM products WHERE id = NEW.product_id),若成立则SIGNAL报错;不能在AFTER中拦截,因数据已变更不可逆。

触发器里怎么判断库存够不够
核心是读取当前库存,和要扣减的数量比大小。别直接用 SELECT ... FOR UPDATE 或事务锁——触发器本身就在事务上下文中,再加显式锁容易死锁或阻塞写入。正确做法是用 IF + 子查询一次性判断:
- 在
BEFORE INSERT或BEFORE UPDATE触发器中检查:如果NEW.quantity > (SELECT stock FROM products WHERE id = NEW.product_id),就抛出错误 - MySQL 用
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '库存不足';PostgreSQL 用RAISE EXCEPTION '库存不足' - 注意子查询必须返回单行单列,否则触发器会报错;加
LIMIT 1或确保WHERE条件唯一
为什么不能在 AFTER 触发器里拦截超卖
AFTER 触发器执行时,原语句(比如 INSERT INTO orders)已经成功提交或回滚,库存字段可能已被更新,但业务逻辑已不可逆。一旦超卖发生,你只能“事后补救”,无法真正阻止。
- 超卖拦截必须发生在数据变更前,即
BEFORE阶段 - 有些开发者想在
AFTER里查库存、发预警,这可以,但拦截动作必须前置 - 若业务要求“允许临时超卖但强预警”,那
AFTER可配合外部消息队列发告警,但别指望它拦住数据写入
实时预警该往哪写日志或发通知
触发器内不适合调用 HTTP、发邮件或写外部系统——多数数据库不支持,且会拖慢事务、引入不稳定因素。
- 安全做法:只往数据库本地表写一条预警记录,比如插入到
warning_log表,字段含product_id、attempted_quantity、current_stock、created_at - 避免在触发器里做复杂计算或连表查询,尤其是带
JOIN或子查询的预警条件,会影响主事务性能 - 如果真要发钉钉/企业微信,应由应用层监听
warning_log表变化(如用 CDC 或定时轮询),而非塞进触发器
并发场景下这个判断还可靠吗
单条触发器判断本身不解决并发竞争。两个事务同时读到库存=10,都判断“够扣5”,结果一起扣减,变成库存=-0。这是典型的“读-改-写”竞态。
- 必须配合行级锁:在触发器的子查询中加上
SELECT stock FROM products WHERE id = NEW.product_id FOR UPDATE - 注意
FOR UPDATE只在事务中生效,而触发器天然属于外层事务,所以可行——但会增加锁等待,需压测验证吞吐影响 - 更优解是放弃触发器,改用应用层+数据库乐观锁(version 字段)或 Redis 原子操作预占库存,触发器只做兜底校验
真正难的不是写触发器,而是确认你的库存扣减路径是否全量经过它。漏掉一个直连 UPDATE products 的脚本,预警和拦截就形同虚设。

















