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段根本看不到。
