多数场景下不建议用sql触发器做库存扣减,因其易掩盖业务逻辑、难以调试,高并发下易致死锁或超卖;真正适合的是旁路校验或日志记录;强一致性应优先用应用层事务+select...for update+redis预减。

触发器在库存扣减中到底该不该用
多数场景下,不建议用 SQL 触发器做库存扣减。它容易掩盖业务逻辑、难以调试,且在高并发下极易引发死锁或超卖——尤其是当 INSERT 订单的同时触发 UPDATE 库存,而库存表又没加合适索引或事务隔离级别时。
真正适合触发器的,是「旁路校验」或「日志记录」这类副作用小、不参与主业务决策的操作。如果业务要求强一致性(比如下单即锁库存),优先走应用层事务 + 行级锁(如 SELECT ... FOR UPDATE),再配合 Redis 预减库存做前置过滤。
真要用触发器,必须绕开的三个坑
若因历史系统限制或审计要求必须用触发器,以下三点不处理,上线后必出问题:
- 触发器内不能调用存储过程或外部函数(尤其涉及网络、文件、时间等待),否则会阻塞事务;
- 触发器里禁止写入同一张表(比如在
orders的AFTER INSERT里再往orders插数据),MySQL 会报错Can't update table 'orders' in stored function/trigger because it is already used by statement which invoked this stored function/trigger; - 触发器无法捕获应用层绕过 SQL 直接调用 ORM 的操作(如 Django 的
bulk_create或 MyBatis 的insert ignore),库存就会悄悄不一致。
一个可用的 MySQL AFTER INSERT 触发器示例
假设订单表 orders 和商品库存表 products 结构如下:
CREATE TABLE orders ( id BIGINT PRIMARY KEY, product_id INT NOT NULL, quantity INT NOT NULL ); CREATE TABLE products ( id INT PRIMARY KEY, stock INT NOT NULL DEFAULT 0 );
下面这个触发器只在插入订单后尝试扣减库存,但做了关键防护:
DELIMITER $$
CREATE TRIGGER reduce_stock_after_order
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
DECLARE current_stock INT DEFAULT 0;
SELECT stock INTO current_stock FROM products WHERE id = NEW.product_id FOR UPDATE;
IF current_stock >= NEW.quantity THEN
UPDATE products SET stock = stock - NEW.quantity WHERE id = NEW.product_id;
ELSE
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insufficient stock';
END IF;
END$$
DELIMITER ;
注意:FOR UPDATE 必须加,否则并发插入相同 product_id 时会读到旧值;SIGNAL 是唯一能中断事务的方式,不能靠 RETURN 或注释掉更新语句来“静默失败”。
PostgreSQL 里触发器更灵活,但代价更高
PostgreSQL 支持 BEFORE INSERT 触发器并返回 NULL 来取消插入,更适合做库存预检:
CREATE OR REPLACE FUNCTION check_stock() RETURNS TRIGGER AS $$ DECLARE available INT; BEGIN SELECT stock INTO available FROM products WHERE id = NEW.product_id; IF available CREATE TRIGGER check_stock_before_order BEFORE INSERT ON orders FOR EACH ROW EXECUTE FUNCTION check_stock();
但它仍无法解决「两个事务同时查到足够库存,然后都扣减」的问题——这本质是应用层要负责的乐观锁或重试逻辑,不是触发器能兜住的。
真正难的从来不是写几行触发器,而是想清楚:你扣的是哪一刻的库存?谁在承担超卖责任?数据库报错时,前端是显示“下单失败”还是自动降级为预售?这些决策点,代码里藏不住,也压不到触发器身上。










