MySQL事务(二)

在上一篇博客中,我们已经学习了:

  • 事务的 ACID 特性

  • 四种隔离级别

  • Read Uncommitted

  • Read Committed

并且知道:

Read Committed 会出现"不可重复读"问题

而 MySQL 默认隔离级别:

复制代码
REPEATABLE READ

却能解决这个问题。

那么:

  • MySQL 是怎么做到的?

  • 为什么普通 SELECT 不加锁也能保证一致性?

  • MVCC 到底是什么?

  • Read View 是什么?

  • 幻读为什么难解决?

这一篇,我们彻底搞懂。


一、Repeatable Read(可重复读)

Repeatable Read:

可重复读

简称:

RR

这是:

  • MySQL默认隔离级别

  • 面试高频核心

  • MVCC最重要的应用场景


RR 的核心目标

保证:

同一个事务中,多次读取同一数据,结果必须一致


二、Read Committed 为什么不行?

在 Read Committed 下:

事务A:

复制代码
BEGIN;

SELECT balance FROM account WHERE id = 1;

结果:

复制代码
100

事务B:

复制代码
UPDATE account SET balance = 200 WHERE id = 1;

COMMIT;

事务A再次读取:

复制代码
SELECT balance FROM account WHERE id = 1;

结果:

复制代码
200

同一个事务:

两次读取结果不同。

这就是:

不可重复读


原因

因为:

RC 每次 SELECT 都读取"最新已提交数据"。

所以:

别人提交之后:

当前事务立刻就能看到。


三、Repeatable Read 如何解决?

RR 的核心思想:

第一次读取时,生成一个"数据快照"。

之后:

  • 不再读取最新数据

  • 而是读取事务开始时看到的数据

因此:

即使别人修改并提交:

当前事务依然读取旧值。


四、RR实验演示

事务A

复制代码
BEGIN;

SELECT balance FROM account WHERE id = 1;

结果:

复制代码
100

事务B

复制代码
UPDATE account SET balance = 200 WHERE id = 1;

COMMIT;

事务A再次读取

复制代码
SELECT balance FROM account WHERE id = 1;

结果仍然:

复制代码
100

为什么?

因为:

RR读取的不是最新数据

而是:

"事务开始时的数据快照"


五、MVCC(多版本并发控制)

RR 能实现的核心:

MVCC

全称:

Multi-Version Concurrency Control

即:

多版本并发控制


六、为什么需要 MVCC?

假设数据库只有一份数据:

复制代码
balance = 100

事务A正在读取。

此时事务B修改:

复制代码
balance = 200

那么:

事务A就无法继续读取旧值


于是:

MySQL想到一个办法:

不直接覆盖旧数据。

而是:

保留多个版本


七、MVCC 的核心思想

假设:

最开始:

复制代码
100

之后有人修改:

复制代码
200

再修改:

复制代码
300

实际上:

数据库内部更像:

复制代码
300
 ↓
200
 ↓
100

形成:

版本链

不同事务:

读取不同版本。


八、MVCC 的优点

1. 普通SELECT不加锁

例如:

复制代码
SELECT * FROM account;

不会阻塞写操作。


2. 写操作也不阻塞读

相比传统:

复制代码
读加锁
写加锁

MVCC并发能力高很多。


3. 实现可重复读

事务始终读取:

自己事务开始时的版本。


九、隐藏字段

InnoDB 每一行数据后面:

实际上隐藏了几个字段。

虽然我们平时看不到。

但它们非常重要。


1. trx_id

表示:

最后一次修改该行的事务ID

例如:

复制代码
事务10修改了该行

那么:

复制代码
trx_id = 10

2. roll_pointer

指向:undo log

即:

旧版本数据。


十、undo log(回滚日志)

undo log:回滚日志

是MVCC真正保存历史版本的位置。


十一、undo log 的作用

1. 事务回滚

例如:

复制代码
ROLLBACK;

数据库需要恢复旧值。


2. MVCC读取历史版本

事务读取快照时:

实际上会:

复制代码
沿着undo log查找旧版本

十二、版本链

假设:

数据经历:

复制代码
100 -> 200 -> 300

内部实际上:

复制代码
300
 ↓
200
 ↓
100

通过:

复制代码
roll_pointer

串起来。

这就形成:

版本链


十三、Read View(读视图)

MVCC 中最核心的概念:

Read View


一、什么是 Read View?

可以理解为:

当前事务观察数据库的"视角"。


二、什么时候生成?

RR 下:

第一次 SELECT 时生成

之后:

整个事务复用同一个 Read View。


三、作用

决定:

复制代码
哪些版本当前事务可以看到

十四、Read View 的简单理解

事务启动时:

数据库会记录:

复制代码
当前有哪些事务正在活跃

之后判断:

复制代码
某个数据版本是否对当前事务可见

举个简单例子

假设:

当前:

复制代码
事务5正在运行
事务6正在运行
事务7正在运行

事务8开始读取。

那么:

Read View 会记录:

复制代码
[5,6,7]

之后:

如果某数据是:

复制代码
事务6修改的

但事务6还没提交。

那么:

事务8不能看到。


十五、快照读 与 当前读

1. 快照读(Snapshot Read)

普通 SELECT:

复制代码
SELECT * FROM account;

特点:

  • 使用MVCC

  • 不加锁

  • 读取历史快照


2. 当前读(Current Read)

读取最新数据:

并加锁。

例如:

复制代码
SELECT * FROM account FOR UPDATE;

或者:

复制代码
UPDATE ...
DELETE ...
INSERT ...

这些都属于:

当前读


当前读特点

  • 读取最新版本

  • 会加锁

  • 可能阻塞


十六、为什么 RR 能读到自己修改的数据?

这是很多人第一次学MVCC时最困惑的点。


事务A:

复制代码
BEGIN;

SELECT balance FROM account WHERE id = 1;

结果:

复制代码
100

事务A自己修改:

复制代码
UPDATE account SET balance = 500 WHERE id = 1;

再次查询:

复制代码
SELECT balance FROM account WHERE id = 1;

结果:

复制代码
500

不是说:

复制代码
RR读取旧快照吗?

为什么又变成500了?


原因

因为:

事务永远能看到自己修改的数据。

MySQL 会特殊处理:

复制代码
自己写的数据,对自己始终可见

十七、幻读(Phantom Read)

幻读:

同一个事务中,两次查询的数据"数量"不一致。


示例

事务A:

复制代码
BEGIN;

SELECT * FROM student WHERE age = 18;

结果:

复制代码
3条数据

事务B:

复制代码
INSERT INTO student VALUES(...,18);

COMMIT;

事务A再次查询:

复制代码
SELECT * FROM student WHERE age = 18;

结果:

复制代码
4条数据

像突然"冒出来"一条。

因此:

幻读


十八、MySQL 如何解决幻读?

理论上:

RR 不能完全解决幻读。

但是:

InnoDB 对幻读做了很强的优化。

核心:

Next-Key Lock(临键锁)


十九、Next-Key Lock(临键锁)

本质:

复制代码
行锁 + 间隙锁

不仅锁当前行。

还锁:

复制代码
数据之间的间隙

为什么锁间隙?

为了防止:

复制代码
别人插入新数据

从而避免幻读。


二十、Serializable(串行化)

最高隔离级别:

SERIALIZABLE


特点

所有事务:

复制代码
排队执行

类似:

复制代码
单线程

优点

不会出现:

  • 脏读

  • 不可重复读

  • 幻读


缺点

性能最差。

并发能力最低。

因此:

生产环境极少使用。


二十一、四种隔离级别总结

隔离级别 脏读 不可重复读 幻读
Read Uncommitted
Read Committed 不会
Repeatable Read 不会 不会 基本不会
Serializable 不会 不会 不会

二十二、为什么 MySQL 默认选择 RR?

原因:


1. 一致性更强

相比RC:

  • 避免不可重复读

  • 数据更稳定


2. MVCC性能优秀

虽然隔离更强。

但是:

复制代码
普通SELECT不加锁

因此性能仍然很高。


3. InnoDB优化了幻读问题

通过:

  • MVCC

  • Next-Key Lock

实现:

复制代码
高隔离 + 高性能

二十三、事务底层原理总结

事务底层最核心:


一、原子性

通过:

undo log

实现回滚。


二、持久性

通过:

redo log

保证崩溃不丢数据。


三、隔离性

通过:

  • MVCC

实现。


四、一致性

由:

  • ACID共同保证

最终实现。


二十四、总结

事务真正解决的问题:

复制代码
高并发下的数据一致性

MySQL通过:

  • undo log

  • redo log

  • MVCC

  • Read View

  • Next-Key Lock

  • 锁机制

最终实现:

复制代码
高并发下的数据安全与高性能

好啦,这就是MySQL里面关于事务的知识点啦,接下来我们马上学习视图,再接入我们的项目,就算学完MySQL啦~~

相关推荐
m0_470857641 小时前
CSS如何实现表单元素的统一样式_使用CSS变量控制输入框状态
jvm·数据库·python
会编程的土豆1 小时前
mysql数据类型
数据库·mysql
wang3zc2 小时前
如何正确管理浮层提示(Tooltip)显示时的页面焦点顺序
jvm·数据库·python
2401_824222692 小时前
如何导出Laravel特定时间段的订单数据 基于created_at过滤导出
jvm·数据库·python
2501_901200532 小时前
进阶设计指南之如何打印分页与自适应ER图_支持高级扩展类型
jvm·数据库·python
m0_609160492 小时前
C#怎么实现HttpClient最佳实践 C#如何用IHttpClientFactory管理HttpClient避免端口耗尽【网络】
jvm·数据库·python
南境十里·墨染春水2 小时前
linux 学习进展 mysql 事务详解
linux·学习·mysql
zjy277772 小时前
Quill 编辑器光标意外跳转至顶部的解决方案
jvm·数据库·python
2301_766283442 小时前
MySQL数据误删除后如何快速恢复_基于binlog日志的闪回操作
jvm·数据库·python