MySQL事务隔离级别实现原理:用生活例子讲透,小白3分钟看懂
一、先建立一个真实场景(所有例子都围绕它)
我们开一家「小明奶茶店」,用一张 orders 表记录订单:
CREATE TABLE orders (
id INT PRIMARY KEY,
customer VARCHAR(20),
drink VARCHAR(20),
status ENUM('pending', 'done') DEFAULT 'pending'
);
初始数据:
| id | customer | drink | status |
|---|---|---|---|
| 1 | 小红 | 珍珠奶茶 | pending |
| 2 | 小刚 | 椰果乌龙 | pending |
现在有两位店员(事务A和事务B)同时工作——这就是并发。
二、四种隔离级别,全用「奶茶店日常」举例说明
▶ 场景1:READ UNCOMMITTED(读未提交)→ “偷看草稿本”
-
事务A(店长查漏单):
SELECT * FROM orders WHERE status = 'pending';→ 看到2单(小红、小刚) -
事务B(新店员手抖):
INSERT INTO orders VALUES (3, '小美', '芋圆奶绿', 'pending');
UPDATE orders SET status = 'done' WHERE id = 1;← 但还没点“确认提交”按钮(即未 COMMIT) -
事务A再次查询:
SELECT * FROM orders WHERE status = 'pending';
→ 突然看到3单!还发现小红那单状态变成'done'!
✅ 这就是脏读:A读到了B根本没保存成功的临时操作。
⚠️ 后果:店长以为小美下单了、小红喝完了,结果B一取消,全白忙。
💡 实现原理:不加任何读锁,写操作也只加最轻的共享锁(允许别人读未提交) —— 完全放养。
▶ 场景2:READ COMMITTED(读已提交)→ “只看盖章版”
-
事务A第一次查:
SELECT * FROM orders WHERE status = 'pending';→ 看到2单(小红、小刚) -
事务B操作并提交:
UPDATE orders SET status = 'done' WHERE id = 1;
COMMIT;← ✅ 盖章生效! -
事务A再次查:
SELECT * FROM orders WHERE status = 'pending';→ 只剩1单(小刚)!小红那单不见了。
✅ 解决了脏读(再也看不到未盖章的修改),
❌ 但出现不可重复读:同一事务内,两次查同一行,结果不同。
💡 实现原理:每次 SELECT 都生成新快照(Read View),只包含“已盖章”的事务修改;写时加排他锁,防止覆盖。
▶ 场景3:REPEATABLE READ(可重复读,MySQL默认)→ “锁定今日菜单”
-
事务A第一次查:
SELECT * FROM orders WHERE status = 'pending';→ 看到2单(小红、小刚) -
事务B插入并提交:
INSERT INTO orders VALUES (3, '小美', '芋圆奶绿', 'pending');
COMMIT; -
事务A再次查:
SELECT * FROM orders WHERE status = 'pending';→ 还是2单!完全没看到小美。
✅ 解决了不可重复读(A的世界时间冻结在第一次查询那一刻),
⚠️ 但幻读隐患仍在:如果A执行 UPDATE orders SET status='done' WHERE status='pending';,会更新3单(含小美)!
💡 实现原理:A第一次SELECT时就拍下全局快照(Read View复用),后续所有读都按此快照走;写操作加Next-Key Lock(记录锁+间隙锁),拦住区间插入。
🔑 关键:SELECT不锁表但靠快照隔离;UPDATE/DELETE自动加间隙锁防幻写。
▶ 场景4:SERIALIZABLE(可串行化)→ “一人一窗,排队办”
-
事务A执行:
SELECT * FROM orders WHERE status = 'pending';
→ 系统自动给status='pending'的所有行加读锁 -
事务B此时想插入新单:
INSERT INTO orders VALUES (3, '小美', '芋圆奶绿', 'pending');
→ ⏳ 卡住!等A释放锁(COMMIT或ROLLBACK后才继续)
✅ 彻底消灭脏读、不可重复读、幻读,
❌ 性能暴跌:所有人必须排队,高峰期窗口全堵死。
💡 实现原理:把所有 SELECT 当成
SELECT ... LOCK IN SHARE MODE,读写互斥,物理串行。
三、对比表:一句话看穿本质区别
| 隔离级别 | 小红点单后,小刚能不能立刻看到? | 小刚查两次pending单,数量会变吗? | 小刚查完,小美能插队下单吗? | 店铺效率 |
|---|---|---|---|---|
| 读未提交 | ✅(看到未确认单) | ✅(第二次可能少1单) | ✅(随便插) | ⚡ 最快,但错乱 |
| 读已提交 | ❌(要等小红点“确认”) | ✅(小红确认后,第二次就少了) | ✅(小美随时能插) | 🚀 快,较稳 |
| 可重复读 | ❌(小红确认也不影响小刚快照) | ❌(两次结果绝对一样) | ⚠️(小美能插,但小刚UPDATE时会被锁住) | 🐢 稍慢,极稳 |
| 可串行化 | ❌(小红点单=锁住pending区域) | ❌ | ❌(小美必须等小刚办完) | 🐌 最慢,绝对稳 |
四、真正决定行为的不是“级别名”,而是“你写的SQL类型”
| 你的SQL写法 | 在RR级别下实际行为 | 为什么? |
|---|---|---|
SELECT * FROM orders; | ✅ 读快照,不锁表 | MVCC兜底,无感并发 |
SELECT * FROM orders WHERE id=1; | ✅ 读快照,且对id=1这行加隐式读锁(不阻塞其他读) | 精确匹配,InnoDB优化 |
SELECT * FROM orders WHERE status='pending' FOR UPDATE; | ❌ 不读快照!查最新数据,并对所有status='pending'的行+间隙加写锁 | 显式要求强一致性,触发锁机制 |
UPDATE orders SET status='done' WHERE id=1; | ❌ 不读快照!查最新数据,并对id=1加行锁+前后间隙锁 | 写操作天然绕过MVCC,直面锁 |
✅ 记住:“读”靠MVCC快照,“写”靠锁。级别只是设定默认策略,SQL才是最终裁判。
五、终极口诀(背下来直接用)
读未提交:啥都敢看,错得离谱;
读已提交:只看盖章件,但章会变;
可重复读:给你印一本今日菜单,翻烂都不改;
可串行化:所有人排队,窗口只开一个。
你现在能清晰回答:
🔹 为什么淘宝订单详情页刷新10次数据不变?→ REPEATABLE READ + MVCC快照;
🔹 为什么抢券时总提示“库存不足”?→ SELECT ... FOR UPDATE + Gap Lock锁住库存区间;
🔹 为什么日志系统用READ COMMITTED?→ 要最新数据,不怕短暂不一致,换高性能。
原理不在纸上,在每一行执行的SQL里——动手试一次,比读十篇文章都管用。
参考来源
2201



被折叠的 条评论
为什么被折叠?



