mysql触发器在并发写入时不串行执行,但受事务隔离级别和锁机制约束;其逻辑随主语句在事务内执行,不额外加锁,死锁或锁超时源于多事务竞争同一行或索引范围。

MySQL触发器在并发写入时是否串行执行
不串行,但会受事务隔离级别和锁机制约束——INSERT/UPDATE/DELETE 语句本身加行锁或间隙锁,触发器逻辑随语句一起在事务内执行,不会额外加锁,也不会主动排队。
常见错误现象:Deadlock found when trying to get lock 或 Lock wait timeout exceeded,本质不是触发器“并发不支持”,而是多个事务同时修改同一行(或满足相同索引范围)时,触发器里的语句加剧了锁竞争。
- 触发器内避免访问其他表(尤其是大表、无索引字段的
WHERE),否则容易扩大锁范围 - 不要在
BEFORE触发器里做耗时操作(如调用存储函数、查远程接口),它会拖慢主语句提交 - 若触发器需更新另一张配置表,确保该表有合适主键/唯一索引,且更新条件能命中索引,否则可能升级为表级锁(尤其在
MyISAM或低版本InnoDB)
触发器中调用 SELECT 的并发风险
在 BEFORE 或 AFTER 触发器里读取本表或其他表数据,看似只读,实则可能引发不可预期的锁等待甚至死锁。
使用场景:比如在 orders 表插入前,用触发器查 users 表校验余额;或在 AFTER INSERT 中统计某类订单数并写入汇总表。
-
SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE在触发器里是允许的,但会显式加锁,极易成为并发瓶颈 - 普通
SELECT在READ COMMITTED下通常不加锁,但在REPEATABLE READ(MySQL 默认)下可能触发间隙锁,尤其配合WHERE条件未命中索引时 - 触发器内
SELECT若走全表扫描,会放大 MVCC 版本链开销,在高并发写入下显著拖慢innodb_row_lock_waits
AFTER INSERT 触发器更新汇总表的典型踩坑点
这是最常被用来做“实时计数”的方案,但也是并发下最容易出错的模式——两个事务几乎同时插入,都读到旧值,+1 后写回,导致最终结果比实际少 1。
错误示例:UPDATE stats SET count = count + 1 WHERE type = 'order' 在 AFTER INSERT 里执行,没加锁也没用原子操作。
- 必须改用原子更新:
UPDATE stats SET count = count + 1 WHERE type = 'order'本身是原子的,但前提是type有唯一索引,否则可能锁住多行 - 如果汇总逻辑复杂(比如要查关联表再计算),优先考虑异步方式(如 binlog 解析、消息队列),别硬塞进触发器
- 注意
INSERT ... ON DUPLICATE KEY UPDATE和触发器的交互:某些 MySQL 版本中,ON DUPLICATE KEY UPDATE可能只触发BEFORE INSERT,不触发AFTER INSERT,导致计数漏掉
替代触发器的轻量并发方案(仅限简单场景)
如果只是想保证“主表写入 + 关联字段自动填充/简单计数”,不用触发器也能更稳地实现。
- 用
DEFAULT或GENERATED ALWAYS AS(MySQL 5.7+)处理派生字段,完全规避运行时逻辑 - 计数类需求,直接用
INSERT ... SELECT COUNT(*) FROM ...+ 应用层重试,或上INSERT INTO stats SELECT 'order', COUNT(*) FROM orders WHERE ... GROUP BY ...批量更新 - 真需要强一致性且逻辑复杂?把触发器逻辑提到应用层,用
BEGIN; INSERT; UPDATE; COMMIT;包裹,配合SELECT ... FOR UPDATE显式控制并发边界
触发器不是不能用,而是它的执行时机、事务上下文和锁行为很容易被低估。真正压测时暴露的问题,往往不在语法对错,而在多个触发器嵌套调用、或与外键约束/级联操作叠加后的锁路径爆炸。











