三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

MYSQL 事务原理

MYSQL 事务原理

脏读:一个事务读到了另一个事务尚未提交的修改。

连接1:

连接2:

连接1事务未提交,但是连接2可以读到其修改。

将隔离级别设置为:read commited (读已提交) 可以解决脏读。

不可重复读:同一事务里,两次读同一行,结果不一样。原因是中间有别的事务提交了。

将隔离级别设置为:repeatable read (可重复读) 可以解决不可重复读。

幻读:同一事务里,两次读取同一个范围内的记录得到的结果集不同。当前读和快照读不一样。

在repeatable级别通过加锁解决幻读问题

连接1

连接2:

连接2若想插入,会阻塞等待。当连接1 commit或rollback才会执行。

read uncommited : 脏读、不可重复读、幻读。

read commited : 不可重复读、幻读。

repeatable read : 幻读。

MVCC 是什么

MVCC = Multi-Version Concurrency Control(多版本并发控制)

核心想法:一行数据不只留 当前值,还会保留历史版本。读的时候尽量读某个历史版本,写的时候生成新版本,这样读写常常不用互相锁死。

可以把它想成一行记录有一条 "版本链":

当前版本 (trx=100, balance=300)
↑ undo
旧版本 (trx=80, balance=200)
↑ undo
更旧版本 (trx=50, balance=100)

更新时:写出新版本,旧值放到 undo log,串成链

普通SELECT(快照读):沿版本链往回找,找到「自己可见」的那个版本

SELECT ... FOR UPDATE(当前读):读最新已提交/需要加锁的版本,走锁,不完全靠这套纯快照逻辑。

Read View 是什么

Read View(读视图) 是事务做一致性读时,InnoDB 生成的一份「可见性规则快照」。

它回答的问题只有一个:版本链上这个版本,当前事务能不能看见?

判断逻辑直观理解:

  1. 版本由已提交且在我视野之前的事务产生 → 可见
  2. 版本由我自己产生 → 可见
  3. 版本由生成 Read View 时还未提交的事务产生 → 不可见(继续沿 undo 找更老版本)
  4. 版本由比我视野更晚的事务产生 → 不可见

找到第一个可见版本,就是这次SELECT的结果。

READ COMMITTED

  • 每次普通SELECT新建一个 Read View
  • 所以中间别人COMMIT了,下一次查可能看到新值
  • 可能不可重复读、幻读

REPEATABLE READ

  • 事务里第一次普通SELECT创建 Read View,之后复用
  • 所以同一事务里多次普通查询,视野不变
  • 可重复读;普通SELECT也很难看到新插入的「幻行」

隔离级别差异:很大程度上就是 Read View 创建时机不同(每次读新建 vs 事务内复用)

在 InnoDB 里,读分两种:

快照读(一致性读)

读的是 Read View 下可见的历史版本,一般不加锁(或几乎不挡别人写)。

常见语句:

SELECT * FROM accounts WHERE id = 1;

特点:

  • 走 MVCC + Read View
  • RR 下同一事务多次读,结果通常一致
  • 可能不是磁盘上「此刻最新已提交」的那一版

当前读

读的是 最新版本,并且会加锁,保证读到的行别人不能随便改。

常见语句:

SELECT ... FOR UPDATE; SELECT ... LOCK IN SHARE MODE; -- 或 FOR SHARE UPDATE / DELETE; -- 先当前读再改 INSERT ... SELECT ...; -- 其中的 SELECT 也可能是当前读

特点:

  • 不靠「复用旧 Read View」来读旧版本
  • 要加行锁 / 间隙锁等
  • 能读到最新已提交数据(并锁住,防止并发冲突)

MySQL 锁机制详解

MySQL 锁
├── 表级锁:表锁、意向锁、MDL
└── 行级锁(InnoDB 主力)
├── 模式:共享锁 S / 排他锁 X
└── 范围:记录锁 Record / 间隙锁 Gap / 临键锁 Next-Key

行锁的两种模式:S 和 X

含义常见语句

共享锁 S

允许别人再加 S,不允许加 X

SELECT ... FOR SHARE

排他锁 X

不允许别人再加 S/X

SELECT ... FOR UPDATEUPDATEDELETE

兼容关系很简单:

  • S 与 S:兼容
  • S 与 X:不兼容
  • X 与 X:不兼容

所以:

  • A 持有某行 X 锁时,B 再对同行FOR UPDATE→ B 等待
  • A 只是普通SELECT(快照读)→ 通常不持有行锁,B 的更新不一定被它堵住

行锁的三种范围:Record / Gap / Next-Key

这是 InnoDB 锁最核心的部分。

1. 记录锁(Record Lock)

锁住已经存在的索引记录。

SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

效果:锁住id=1这一行,防止别人改/删它。

2. 间隙锁(Gap Lock)

锁住索引记录之间的空隙,不锁某行本身,但阻止别人往空隙里插入。

例如索引上有id = 10, 20,间隙(10, 20)被锁后:

INSERT INTO t VALUES (15, ...); -- 可能被堵住

间隙锁的主要意义:防止幻读(当前读场景下,别插进新行)。

3. 临键锁(Next-Key Lock)

Next-Key Lock = Record Lock + Gap Lock

InnoDB 在 REPEATABLE READ 下,当前读默认常用这套算法:
锁住一个左开右闭区间,例如(10, 20]

既防止改20,也防止在1020之间插入新记录。

快照读 vs 当前读

这是理解「锁」和「MVCC」分工的关键。

快照读

SELECT * FROM accounts WHERE id = 1;
  • 读的是 Read View 下可见的历史版本
  • 一般不加行锁
  • RR 下同一事务多次普通查询,结果通常稳定

当前读

SELECT * FROM accounts WHERE id = 1 FOR UPDATE; UPDATE accounts SET balance = 200 WHERE id = 1; DELETE FROM accounts WHERE id = 1;
  • 读最新数据
  • 加锁
  • 可能阻塞其他事务的冲突写/插

隔离级别如何影响锁

隔离级别简要特征

READ UNCOMMITTED

可脏读,基本不用于生产

READ COMMITTED

每次普通 SELECT 新 Read View;当前读主要是记录锁,间隙锁很少

REPEATABLE READ(默认)

快照复用;当前读大量使用 Next-Key,防幻读更强

SERIALIZABLE

普通 SELECT 也会趋向加锁读,并发最差、最严格

  • 大多数业务用默认 RR 就够
  • 若热点行冲突严重、且能接受不可重复读,可评估 RC
  • 不要为了“更安全”一上来就 SERIALIZABLE
← 返回列表