触发器不应执行查表、跨表更新等操作,仅保留基于new/old的字段赋值或简单校验;select查表会引发gap lock加剧锁竞争,after触发器更易导致死锁,统计聚合逻辑须移至应用层异步处理。

高并发下触发器引发的行锁竞争,根本不是“怎么写得更优雅”的问题,而是“它本不该干这个事”。直接结论:把查表、跨表更新、函数生成等操作全移出触发器,只保留基于 NEW 或 OLD 的字段赋值或简单校验。
触发器里 SELECT 查其他表为什么放大锁竞争
每次触发都执行 SELECT name FROM staff WHERE id = NEW.operator_id,表面查一行,实际在 REPEATABLE READ 隔离级别下会加 gap lock —— 不仅锁住匹配行,还锁住前后间隙。并发插入日志时,多个事务排队等同一段索引范围锁,TPS 断崖下跌。
- 即使 staff 表有主键索引,
WHERE id = ?也只保证不扫表,但锁仍落在索引页上;高并发写入会让这些页成为热点 - 若 staff 表被其他业务频繁更新,该 SELECT 还可能触发二级索引回表,进一步延长持锁时间
- MySQL 8.0+ 对 AFTER 触发器中 SELECT 的锁行为更严格,5.7 上没报错的逻辑,在 8.0 可能直接抛
Deadlock found when trying to get lock
BEFORE vs AFTER:哪个更容易锁死
AFTER 触发器在高并发下更危险。它在原始语句已持锁的前提下再发起新 SQL,极易形成锁链闭环。比如 AFTER INSERT ON orders 中执行 UPDATE order_summary SET total = total + NEW.amount,order_summary 行锁和 orders 新行锁在同一事务内竞争,别的事务稍一凑近就死锁。
- BEFORE 触发器可直接用
SET NEW.total_amount = ...修改即将插入的值,不发额外 SQL,零新增锁 - 统计类、聚合类逻辑一律移出触发器——改用应用层异步任务、定时 job 或写入
trigger_queue表由后台消费 - 真要更新当前行状态,优先用
INSERT ... ON DUPLICATE KEY UPDATE替代先查后更,减少锁持有窗口
哪些写法会把行锁升级成范围锁甚至表锁
触发器里一个看似无害的 UPDATE product SET status = 'sold' WHERE category_id = 123,若 category_id 没索引或只有非唯一索引,InnoDB 就会锁住整个索引范围,所有插入/更新该分类商品的请求全部阻塞。
-
WHERE条件必须命中**最左前缀的唯一索引或主键**,否则别指望只是“锁一行” - 禁止在触发器里用
OR、LIKE '%abc'、NOT IN等无法走索引的写法;要用EXISTS替代IN子查询 - 避免
SELECT ... FOR UPDATE—— 触发器里显式加锁等于主动制造瓶颈,除非你明确知道只锁单行且索引完全覆盖
真正难的不是写出能跑通的触发器,而是判断哪部分逻辑“看起来合理,实则正在拖垮整个数据库”。每多一次触发器内的 SQL 执行,就多一层锁等待、多一分主从不一致风险、多一个压测时突然崩掉的点。别优化它,替换它。











