MySQL事务调度本质是“锁 + MVCC + 隔离级别”的协同决策,由InnoDB内嵌实现,按隔离级别动态选择读快照、加锁策略与锁生命周期。
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结果可能不一致。
当两个事务同时想改同一行,比如都执行UPDATE users SET balance = balance - 10 WHERE id = 123;,InnoDB不会让它们“协商谁先来”,而是:
X锁(排他锁),继续执行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')还会触发间隙锁,把不存在的“空档”也锁住,导致插入被阻塞——这常被误认为“死锁”,其实是设计使然。
死锁检测不是事后报错,而是每秒运行的后台线程在扫描锁依赖图。一旦发现环形等待(如事务A锁了行1等行2,事务B锁了行2等行1),InnoDB立刻选一个事务回滚(通常是undo log写得少的那个),让另一个继续。这不是bug,是保证系统活性的必要机制。
避免它关键不在“加锁顺序”,而在缩短锁持有时间:
UPDATE、DELETE尽量靠近事务结尾,前面只做查询SELECT ... FOR UPDATE提前锁定,但必须确保WHERE条件精准命中
索引真正难调试的是“隐式锁升级”——比如INSERT在唯一索引冲突时会临时加S锁再转X锁,这种细节不看SHOW ENGINE INNODB STATUS里的LATEST DETECTED DEADLOCK段根本看不到。