MySQL 事务隔离级别与MVCC原理


一、事务ACID

事务 = 把一组SQL操作打包成一个”不可分割的整体”,要么全成功,要么全回滚。

特性一句话实现手段
原子性全做或全不做undo log(回滚日志,存旧值)
一致性数据始终符合业务规则A + I + D 共同保证
隔离性并发事务互不干扰MVCC + 锁
持久性提交后就永久保存redo log(重做日志,崩溃恢复)
  • undo log:改数据前把旧值记下来,回滚时按旧值恢复

  • redo log:提交时先写redo log(顺序追加极快),数据文件异步慢慢刷;崩溃后读redo log补刷


二、并发问题

2.1 脏读(Dirty Read)

  • 读到了另一个事务还没提交的修改

  • 那个事务如果回滚了,你读到的数据就是”脏的”(不存在的)

2.2 不可重复读(Non-Repeatable Read)

  • 同一事务内,两次读同一条数据,结果不一样

  • 原因:中间有另一个事务UPDATE并提交

  • 关注点:行的变了

2.3 幻读(Phantom Read)

  • 同一事务内,两次查同一条件,结果的行数不一样

  • 原因:中间有另一个事务INSERT/DELETE并提交了

  • 关注点:结果集的行数变了

问题发生了什么谁导致的
脏读读到了未提交的修改其他事务回滚
不可重复读两次读同一条,值不同其他事务UPDATE
幻读两次读同一条件,行数不同其他事务INSERT/DELETE

三、四种隔离级别

隔离级别脏读不可重复读幻读(快照读)幻读(当前读)性能
READ UNCOMMITTED❌有❌有❌有❌有最快
READ COMMITTED✅防❌有❌有❌有
REPEATABLE READ✅防✅防✅防⚠️部分防
SERIALIZABLE✅防✅防✅防✅防最慢
  • MySQL默认:REPEATABLE READ

  • Oracle/PostgreSQL默认:READ COMMITTED

  • 很多互联网公司实际用RC(并发性能更好,应用层可接受少量不一致)

RR没完全防住幻读的原因

  • 普通SELECT(快照读):基于MVCC读事务开始时的版本,能防幻读

  • UPDATE/DELETE/SELECT FOR UPDATE(当前读):读最新提交版本,能看到新插入的行,可能产生幻读


四、MVCC原理

4.1 核心思想

  • 每一行数据维护多个历史版本

  • 事务根据自己的”出生时间”看对应版本的数据

  • 读不加锁,读写不冲突,并发能力大幅提升

4.2 三大组件

隐藏列

每行数据(InnoDB表)都有三个隐藏列:

  • DB_TRX_ID:最后一次修改这一行的事务ID(6字节)

  • DB_ROLL_PTR:指向undo log的指针,指向旧版本(7字节)

  • DB_ROW_ID:隐藏主键(没指定主键时用,6字节)

Undo Log版本链

  • 当前行通过DB_ROLL_PTR指向undo log中的旧版本

  • 旧版本又通过自己的DB_ROLL_PTR指向更旧的版本

  • 形成一条”历史版本链条”,从最新追溯到最初插入时

Read View(快照)

Read View包含四个字段:

  • m_ids:当前活跃(未提交)的事务ID列表

  • min_trx_id:活跃事务中最小的ID

  • max_trx_id:下一个新事务的ID(系统当前最大ID + 1)

  • creator_trx_id:创建Read View的事务自己的ID

4.3 可见性判断规则

拿到一个版本的DB_TRX_ID,按顺序判断:

 
1. 是自己改的? → 可见
 
2. trx_id < min_trx_id? → 可见(老早就提交了)
 
3. trx_id >= max_trx_id? → 不可见(还没出生,往前找旧版本)
 
4. 在min和max之间:
 
   - 在m_ids里 → 不可见(还在晃悠没提交,往前找旧版本)
 
   - 不在m_ids里 → 可见(已经提交了)
 

4.4 RC和RR的区别

READ COMMITTEDREPEATABLE READ
Read View创建时机每次SELECT都重新创建事务开始时创建一次,整个事务复用
结果每次SELECT可能看到不同结果整个事务期间看到的数据完全一样
防止不可重复读不能(每次重建快照)能(一个快照用到底)

五、快照读 vs 当前读

快照读当前读
触发SQL普通SELECTUPDATE/DELETE/SELECT ... FOR UPDATE
走不走MVCC走,读历史版本不走,读最新提交版本
加不加锁不加锁加锁(行锁/间隙锁)
能看到别人提交吗看不到能看到

六、间隙锁(Gap Lock)

  • RR隔离级别下,对当前读不光锁住已有行,还锁住索引之间的空隙

  • 防止其他事务在该范围内INSERT(防幻读)

  • Next-Key Lock = 行锁 + 间隙锁(InnoDB在RR下的默认加锁方式)

  • 副作用:降低并发——即使操作不同位置也可能互相阻塞


七、锁机制补充

行锁两种模式

锁类型含义触发方式
共享锁(S锁/读锁)可以读,别人也能读,但不能改SELECT ... FOR SHARE
排他锁(X锁/写锁)只有你能读写,别人读写都不行UPDATE/DELETE/SELECT ... FOR UPDATE

关键易错点

  • InnoDB行锁锁的是索引

  • 如果WHERE条件没走索引,行锁退化成表锁

  • UPDATE user SET age = 30 WHERE name = '张三' — name没索引 → 全表加锁


八、一条UPDATE的全流程

 
1. 通过B+树定位到数据行所在的页 → 读入Buffer Pool
 
2. 加排他锁(X锁)
 
3. 记undo log(存旧值)
 
4. 修改Buffer Pool里的数据页
 
5. 记redo log(顺序追加)
 
6. COMMIT → 刷redo log到磁盘 → 持久性保证
 
7. 后台线程慢慢把Buffer Pool中改过的页刷到磁盘
 

九、binlog与redo log的区别

redo logbinlog
层面InnoDB引擎层MySQL Server层
内容物理日志(页的修改)逻辑日志(SQL语句或行变化)
写入方式循环写,空间固定追加写,文件可切换
用途崩溃恢复(保证持久性)主从复制、数据恢复
写入时机事务执行中不断写事务提交时一次性写

十、速答

问题一句话答案
ACID怎么实现?A:undo log, C:A+I+D, I:MVCC+锁, D:redo log
脏读/不可重复读/幻读区别?脏读=读未提交的值,不可重复读=同一行值变了(UPDATE),幻读=行数变了(INSERT)
MySQL默认隔离级别?REPEATABLE READ
RC和RR根本区别?RC每次SELECT建新Read View;RR一个事务建一次用到底
MVCC是什么?每行多个历史版本,Read View决定能看到哪些版本
RR能完全防幻读吗?快照读能(MVCC),当前读不能(需间隙锁)
InnoDB行锁锁的是什么?锁索引。没走索引时退化成表锁

十一、一图流(Mermaid)


flowchart TD

    A[并发事务] --> B{隔离?}

    B -->|不隔离| C[脏读/不可重复读/幻读]

    B -->|隔离 RR 默认| E[REPEATABLE READ 默认]

    E --> F{读的方式}

    F -->|快照读 SELECT| G[MVCC: 一个 Read View 用到底<br/>防脏读+不可重复读+快照幻读<br/>读不加锁, 读写不阻塞]

    F -->|当前读 UPDATE/DELETE| H[加锁: 行锁+间隙锁<br/>Next-Key Lock 防当前读幻读]

    G --> I[解决 读写冲突]

    H --> J[解决 写写冲突]

速记卡(面试闪卡)

Q1:一句话讲清「MySQL 事务隔离级别与MVCC原理」到底是什么?

A:事务隔离级别规定并发事务能看到什么数据,MVCC用多版本机制让读写互不阻塞。

Q2:一、事务ACID —— 怎么理解?

A:像发工资:要么整笔到账要么不发(原子性),账面始终对得上(一致性),俩人同时查互不串(隔离性),发了就改不了(持久性)。英文统称 ACID(Atomicity/Consistency/Isolation/Durability,原子性/一致性/隔离性/持久性)。

Q3:二、并发问题 —— 怎么理解?

A:像三个人同时改一张表:读到别人没提交的值(脏读 Dirty Read),同一条两次读出不一样(不可重复读 Non-Repeatable Read,值变了),同一条件两次查行数不同(幻读 Phantom Read,行多了)。三个坑就是并发不隔离的代价。

Q4:三、四种隔离级别 —— 怎么理解?

A:像四档窗帘:READ UNCOMMITTED 全透明(啥都能看到)最乱;READ COMMITTED 只信已提交;REPEATABLE READ(MySQL默认)整事务一个视角;SERIALIZABLE 全串行最稳但最慢。隔离档越高越安全越慢。

Q5:四、MVCC原理 —— 怎么理解?

A:像给每行数据拍连续快照:每行藏 DB_TRX_ID(谁改的)、回滚指针串成旧版本链,事务拿着自己的 Read View 按规则看”该看的那一版”。读不加锁、读写不打架。MVCC(Multi-Version Concurrency Control,多版本并发控制)。

Q6:核心速记主线有哪些?

  • 隔离级别:RU / RC / RR / SERIALIZABLE,MySQL 默认 RR

  • 并发三害:脏读、不可重复读、幻读(RR 快照读能防、当前读不能)

  • MVCC 三件套:隐藏列 + undo log 版本链 + Read View

  • 行锁锁索引,没走索引退化成表锁

口诀

A:事务隔离管可见,MVCC 多版读写闲;

ACID 四性各有主,undo redo 锁相连;

RC 每次新快照,RR 一个用到底;

行锁锁在索引上,没索引就变表锁。

相关链接