SQL触发器在高并发秒杀中会引发锁冲突、死锁和性能断崖式下降,因其将异步逻辑强行嵌入核心事务路径,延长行锁持有时间、扩大锁范围,并无法处理乐观校验失败留痕等业务需求。

高并发秒杀场景下,SQL触发器不是“不够快”,而是会直接让事务卡死、锁住关键行、放大死锁概率——它把本该异步或应用层控制的逻辑,硬塞进毫秒级争抢的数据库写入路径里。
触发器在秒杀INSERT/UPDATE中会隐式延长行锁持有时间
秒杀核心是 UPDATE inventory SET qty = qty - 1 WHERE sku = ? AND qty >= 1,但一旦加了AFTER UPDATE触发器去写日志或更新统计表,锁就不再只落在inventory单行上:
- 触发器内
SELECT qty FROM stock WHERE sku = NEW.sku(即使有索引)在REPEATABLE READ下会加gap lock,阻塞其他并发扣减请求 - 若触发器还执行
UPDATE order_summary SET total_orders = total_orders + 1 WHERE date = CURDATE(),就会同时持inventory行锁和order_summary行锁,另一事务若先更新order_summary再查inventory,立刻ABBA死锁 -
Innodb_row_lock_waits指标会在大促开始后5分钟内飙升300%,而慢日志里只显示主SQL,根本看不到触发器在锁什么
批量扣减或库存预占时触发器会退化为串行瓶颈
秒杀常通过 INSERT INTO prelock (sku, user_id) SELECT 'SKU123', ? FROM DUAL WHERE (SELECT qty FROM inventory WHERE sku = 'SKU123') >= 1 预占,但若prelock表有AFTER INSERT触发器:
- 1000人同时预占 → 触发器执行1000次,哪怕每次只查一次
inventory,也等于1000次独立索引查找+间隙锁申请 - 触发器里用
SELECT COUNT(*) FROM prelock WHERE sku = NEW.sku统计当前预占数?没索引就是全表扫描,Rows_examined每条达数万 - MySQL不会并行执行这些触发器,TPS从理论5000直接掉到不足200,且
Threads_running长期卡在80+,连接池被拖垮
触发器无法处理秒杀特有的“乐观校验失败但需留痕”需求
真实秒杀链路中,库存扣减失败(qty )时,业务仍要记录“抢购失败流水”,用于限流分析和用户通知。但触发器做不到:
- BEFORE UPDATE里用
IF NEW.qty → 整个事务回滚,失败记录也丢弃 - AFTER UPDATE里判断
OLD.qty = NEW.qty意味着没扣成功,想INSERT失败日志?不行——触发器不能控制主事务是否提交,日志写不进去 - 想用存储过程封装?调用过程若含COMMIT,直接报
ERROR 1422;不加COMMIT,又没法异步落库
真正能扛住秒杀流量的方案,从来不是靠触发器“自动补全”,而是把日志、统计、通知全部剥离出事务:用INSERT INTO sync_log极简写入,再由Kafka消费者异步分发。否则,你优化的不是性能,是在给死锁和锁等待铺路。










