MySQL事务调度本质是“锁 + MVCC + 隔离级别”的协同决策,由InnoDB内嵌实现,按隔离级别动态选择读快照、加锁策略与锁生命周期。

MySQL事务调度本质是“锁 + MVCC + 隔离级别”的协同决策
MySQL本身没有独立叫“事务调度器”的可配置模块,它的并发调度逻辑内嵌在InnoDB存储引擎中,由隔离级别、行锁机制和MVCC多版本控制三者实时协同决定——不是先排队再执行,而是“边读边判、边写边锁、边查边快照”。比如你执行SELECT ... FOR UPDATE,InnoDB立刻加X锁并阻塞其他写;而普通SELECT在REPEATABLE READ下则直接走当前事务的ReadView找快照版本,不争锁也不等。
不同隔离级别对应完全不同的并发路径
别只记“RR默认”,要清楚每种级别背后触发的是哪套机制:
-
READ UNCOMMITTED:跳过MVCC和锁检查,直接读最新行数据(可能脏读),几乎不调度,纯性能模式 -
READ COMMITTED:每次SELECT都生成新ReadView,只读已提交版本;UPDATE仍需加X锁,但锁释放得早(语句结束即放) -
REPEATABLE READ(InnoDB默认):事务首次SELECT时建一次ReadView,后续全用它;UPDATE加X锁+间隙锁(防止幻读),锁持续到事务结束 -
SERIALIZABLE:所有SELECT自动转为SELECT ... LOCK IN SHARE MODE,强制读锁,彻底串行化
实操建议:set transaction isolation level read committed;在高并发读写混合场景(如订单状态轮询+更新)比默认RR更少锁等待,但你要接受同一事务内两次SELECT结果可能不一致。
写冲突时,InnoDB靠“记录锁+间隙锁”抢夺执行权
当两个事务同时想改同一行,比如都执行UPDATE users SET balance = balance - 10 WHERE id = 123;,InnoDB不会让它们“协商谁先来”,而是:
- 事务A先到达:获取该行的
X锁(排他锁),继续执行 - 事务B后到达:发现锁被占,进入
innodb_lock_wait_timeout等待队列(默认50秒) - 若超时未获锁,报错
ERROR 1205 (40001): Deadlock found when trying to get lock或Lock wait timeout exceeded
注意坑点:WHERE条件没走索引?那会升级成表级锁,整个表卡住;UPDATE带范围条件(如WHERE created_at > '2025-01-01')还会触发间隙锁,把不存在的“空档”也锁住,导致插入被阻塞——这常被误认为“死锁”,其实是设计使然。
死锁不是异常,是InnoDB主动裁决的结果
死锁检测不是事后报错,而是每秒运行的后台线程在扫描锁依赖图。一旦发现环形等待(如事务A锁了行1等行2,事务B锁了行2等行1),InnoDB立刻选一个事务回滚(通常是undo log写得少的那个),让另一个继续。这不是bug,是保证系统活性的必要机制。
避免它关键不在“加锁顺序”,而在缩短锁持有时间:
- 把
UPDATE、DELETE尽量靠近事务结尾,前面只做查询 - 避免在事务里调外部HTTP接口或长循环
- 用
SELECT ... FOR UPDATE提前锁定,但必须确保WHERE条件精准命中索引
真正难调试的是“隐式锁升级”——比如INSERT在唯一索引冲突时会临时加S锁再转X锁,这种细节不看SHOW ENGINE INNODB STATUS里的LATEST DETECTED DEADLOCK段根本看不到。









