MySQL 事务管理全解:从 ACID 特性、隔离级别到 MVCC 底层原理


🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!


🎬 博主简介:


文章目录

  • 前言:
  • [一. 开篇:没有事务,会出现什么问题?](#一. 开篇:没有事务,会出现什么问题?)
  • [二. 什么是事务?](#二. 什么是事务?)
  • [三. 事务的四大核心特性:ACID](#三. 事务的四大核心特性:ACID)
    • [3.1 原子性(Atomicity)](#3.1 原子性(Atomicity))
    • [3.2 一致性(Consistency)](#3.2 一致性(Consistency))
    • [3.3 隔离性(Isolation)](#3.3 隔离性(Isolation))
    • [3.4 持久性(Durability)](#3.4 持久性(Durability))
  • [四. MySQL 事务的版本支持](#四. MySQL 事务的版本支持)
    • [4.1 查看数据库支持的引擎](#4.1 查看数据库支持的引擎)
  • [五. 事务的提交方式](#五. 事务的提交方式)
    • [5.1 查看当前提交方式](#5.1 查看当前提交方式)
    • [5.2 修改自动提交模式](#5.2 修改自动提交模式)
  • [六. 事务的核心操作(含完整实操案例)](#六. 事务的核心操作(含完整实操案例))
    • [6.1 事务基础操作语法](#6.1 事务基础操作语法)
    • [6.2 正常演示:事务的开启、保存点、回滚与提交](#6.2 正常演示:事务的开启、保存点、回滚与提交)
    • [6.3 异常场景验证:事务的原子性与持久性](#6.3 异常场景验证:事务的原子性与持久性)
      • [实验 1:未 commit,客户端崩溃,MySQL 自动回滚](#实验 1:未 commit,客户端崩溃,MySQL 自动回滚)
      • [实验 2:commit 后,客户端崩溃,数据永久生效](#实验 2:commit 后,客户端崩溃,数据永久生效)
      • [实验 3:begin 会自动忽略 autocommit 设置](#实验 3:begin 会自动忽略 autocommit 设置)
    • [6.4 事务操作注意事项](#6.4 事务操作注意事项)
  • [七. 事务隔离级别:解决并发读写的核心](#七. 事务隔离级别:解决并发读写的核心)
    • [7.1 并发事务带来的 3 个核心问题](#7.1 并发事务带来的 3 个核心问题)
    • [7.2 MySQL 的 4 种隔离级别](#7.2 MySQL 的 4 种隔离级别)
    • [7.3 隔离级别的查看与设置](#7.3 隔离级别的查看与设置)
    • [7.4 4 种隔离级别实操演示](#7.4 4 种隔离级别实操演示)
  • [八. 一致性:事务的最终归宿](#八. 一致性:事务的最终归宿)
  • [九. 进阶原理:MVCC 多版本并发控制](#九. 进阶原理:MVCC 多版本并发控制)
    • [9.1 MVCC 解决了什么问题?](#9.1 MVCC 解决了什么问题?)
    • [9.2 MVCC 的三大前置知识](#9.2 MVCC 的三大前置知识)
    • [9.3 版本可见性判断规则](#9.3 版本可见性判断规则)
    • [9.4 当前读 vs 快照读](#9.4 当前读 vs 快照读)
    • [9.5 RC 与 RR 隔离级别的本质区别](#9.5 RC 与 RR 隔离级别的本质区别)
  • [十. 总结与面试重点](#十. 总结与面试重点)
  • 结尾:

前言:

大家好,我是深耕 MySQL 内核与实战优化的博主。在业务开发中,事务是保证数据安全与一致性的核心基石 ,小到银行转账、火车票售票,大到电商订单系统,都离不开事务的支撑。但很多人对事务的理解只停留在begin/commit/rollback,对 ACID 特性、隔离级别、MVCC 底层原理一知半解,最终导致线上出现超卖、数据不一致、脏写等严重问题。


一. 开篇:没有事务,会出现什么问题?

我们先从一个经典的火车票售票系统场景切入,这也是所有业务并发场景的缩影:

有一张火车票库存表tickets,数据如下:

id name nums
10 西安 <-> 兰州 1

此时有两个客户端同时抢这张票,执行逻辑完全一致

  • 检查票数是否大于 0
  • 如果有票,执行卖票逻辑,将票数 - 1

在没有事务控制的情况下,会出现如下时序问题

  • 客户端 A 检查票数,发现还有 1 张票,准备执行更新,但还没写入数据库;
  • 客户端 B 同时检查票数,也看到了 1 张票,同样进入卖票逻辑;
  • 客户端 A 执行update tickets set nums = nums-1 where id=10,票数变为 0;
  • 客户端 B 也执行更新,最终票数变为 - 1,同一张票被卖了两次,出现超卖问题。

要解决这个问题,就需要让整个抢票流程满足 4 个核心要求:

  • 抢票的整个过程必须是原子的,要么全成,要么全败;
  • 两个抢票的操作不能互相干扰
  • 卖票成功后,数据必须永久生效,不能因为宕机丢失;
  • 卖票前后,库存数据的状态必须是一致的,不能出现负数库存。

而这 4 个要求,正是 MySQL 事务的四大核心特性 ------ACID


二. 什么是事务?

事务是由一组逻辑相关的 DML 语句组成的执行单元,这组语句要么全部执行成功,要么全部执行失败回滚,是一个不可分割的整体。MySQL 提供了完整的事务机制,来保证我们的业务逻辑在并发场景下的数据安全。

举个最贴合的例子:毕业时学校教务系统要删除你的所有信息,需要同时删除你的个人基本信息、各科成绩、在校表现、论坛发帖记录等,这多条 SQL 语句组合起来,就构成了一个完整的事务 ------ 要么所有信息全部删除成功,要么一条都不删,绝不会出现只删了一半的情况。



三. 事务的四大核心特性:ACID

ACID 是事务的灵魂,也是面试必问的核心考点,四个特性环环相扣,缺一不可。

3.1 原子性(Atomicity)

原子性指的是:一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。

事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。就像银行转账,A 扣钱和 B 加钱必须同时成功,只要有一步失败,两边的钱都要恢复到初始状态。

3.2 一致性(Consistency)

一致性指的是:事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

这里的完整性包括数据的精确度、串联性、业务规则约束。比如转账场景中,转账前后 A 和 B 的账户总金额必须保持一致;售票场景中,库存不能出现负数。一致性是事务的最终归宿,原子性、隔离性、持久性,都是为了保证数据库的一致性。

3.3 隔离性(Isolation)

隔离性指的是:数据库允许多个并发事务同时对数据进行读写和修改,隔离性可以防止多个事务并发执行时,因为交叉执行导致的数据不一致。

多个事务同时操作同一张表、同一行数据时,就像多个同学同时在一个教室学习,隔离性就是给每个同学拉上了帘子,保证大家的学习互不干扰。MySQL 提供了 4 种隔离级别,来适配不同的业务并发场景,后面会详细拆解。

3.4 持久性(Durability)

持久性指的是:事务处理结束后,对数据的修改就是永久的,即便系统故障、数据库宕机,修改的数据也不会丢失。

事务一旦提交成功,数据就会被持久化到磁盘中,不会因为任何故障回滚。比如你在 ATM 机取了钱,交易提交成功后,哪怕银行机房断电,你的账户扣款记录也不会消失。


四. MySQL 事务的版本支持

不是所有 MySQL 引擎都支持事务,只有 InnoDB 引擎支持完整的事务特性,这也是 InnoDB 成为 MySQL 默认引擎的核心原因之一,而 MyISAM 引擎完全不支持事务。

4.1 查看数据库支持的引擎

我们可以通过以下命令查看当前 MySQL 支持的所有引擎,以及事务支持情况:

sql 复制代码
-- 表格形式展示
show engines;

-- 行形式展示,查看更清晰
show engines \G

执行后核心结果如下:

sql 复制代码
*************************** 1. row ***************************
      Engine: InnoDB
     Support: DEFAULT
     Comment: Supports transactions, row-level locking, and foreign keys
Transactions: YES
          XA: YES
  Savepoints: YES
*************************** 5. row ***************************
      Engine: MyISAM
     Support: YES
     Comment: MyISAM storage engine
Transactions: NO
          XA: NO
  Savepoints: NO

可以清晰看到,InnoDB 的Transactions字段为YES,支持事务;而 MyISAM 为NO,不支持事务。


五. 事务的提交方式

MySQL 事务有两种提交方式:自动提交手动提交,我们可以通过参数控制。


5.1 查看当前提交方式

MySQL 事务有两种提交方式:自动提交手动提交,我们可以通过参数控制。

sql 复制代码
show variables like 'autocommit';

默认结果:

Variable_name Value
autocommit ON

autocommit=ON表示开启自动提交:默认情况下,我们执行的每一条 DML 语句,都会被 MySQL 自动封装成一个事务,执行完成后自动提交。

5.2 修改自动提交模式

sql 复制代码
-- 关闭自动提交
set autocommit=0;

-- 开启自动提交
set autocommit=1;

注意

  • autocommit=0关闭自动提交后,你执行的所有 DML 语句都不会立即生效,需要手动执行commit才会提交,执行rollback会回滚;
  • 只要我们执行begin/start transaction手动开启一个事务,无论autocommit是开启还是关闭,都必须手动commit才会提交。

六. 事务的核心操作(含完整实操案例)

我们先创建一张银行账户测试表,后续所有演示都基于这张表:

sql 复制代码
-- 创建账户表,必须使用InnoDB引擎
create table if not exists account (
    id int primary key,
    name varchar(50) not null default '',
    blance decimal(10,2) not null default 0.0
) engine=innodb default charset=utf8;

6.1 事务基础操作语法

操作语句 作用说明
begin; / start transaction; 手动开启一个事务,推荐使用begin,更简洁
commit; 提交事务,将事务中所有修改永久生效
rollback; 回滚事务,撤销事务中所有未提交的修改,恢复到事务开启前的状态
savepoint 保存点名; 创建事务保存点,用于部分回滚
rollback to savepoint 保存点名; 回滚到指定的保存点,不影响保存点之前的操作

6.2 正常演示:事务的开启、保存点、回滚与提交

sql 复制代码
-- 1. 查看自动提交状态,默认是开启的
show variables like 'autocommit';

-- 2. 开启一个事务
begin;

-- 3. 创建第一个保存点
savepoint save1;

-- 4. 插入第一条记录
insert into account values (1, '张三', 100);

-- 5. 创建第二个保存点
savepoint save2;

-- 6. 插入第二条记录
insert into account values (2, '李四', 10000);

-- 7. 查看数据,两条记录都存在
select * from account;

-- 8. 回滚到save2,撤销第二条插入语句
rollback to save2;

-- 9. 再次查看,只剩张三的记录
select * from account;

-- 10. 直接回滚到事务开启前,撤销所有操作
rollback;

-- 11. 最终查看,表内无数据
select * from account;

6.3 异常场景验证:事务的原子性与持久性

通过 4 个核心实验,验证了事务的核心特性,这里完整还原(SQL 全小写):

实验 1:未 commit,客户端崩溃,MySQL 自动回滚

sql 复制代码
-- 终端A操作
select * from account; -- 表内无数据
show variables like 'autocommit'; -- autocommit=ON
begin; -- 开启事务
insert into account values (1, '张三', 100);
select * from account; -- 能看到插入的数据,此时未commit
-- 此时强制终止终端(ctrl+\ 异常退出)

-- 终端B操作
-- 终端A崩溃前,能读到未提交的数据(读未提交隔离级别下)
select * from account;
-- 终端A崩溃后,再次查询,数据自动回滚,表内无数据
select * from account;

结论:事务未提交时,客户端异常崩溃,MySQL 会自动回滚整个事务,保证原子性。

实验 2:commit 后,客户端崩溃,数据永久生效

sql 复制代码
-- 终端A操作
show variables like 'autocommit'; -- autocommit=ON
select * from account; -- 表内无数据
begin;
insert into account values (1, '张三', 100);
commit; -- 提交事务
-- 强制终止终端(ctrl+\ 异常退出)

-- 终端B操作
-- 终端A崩溃后,查询数据依然存在,不会回滚
select * from account;

结论:事务一旦 commit 提交,数据就会持久化到磁盘,无论客户端是否崩溃,数据都不会丢失,符合持久性特性。

实验 3:begin 会自动忽略 autocommit 设置

sql 复制代码
-- 终端A操作
select * from account; -- 已有张三的记录
show variables like 'autocommit'; -- autocommit=ON
set autocommit=0; -- 关闭自动提交
show variables like 'autocommit'; -- autocommit=OFF
begin; -- 开启事务
insert into account values (2, '李四', 10000);
select * from account; -- 能看到李四的记录
-- 强制终止终端

-- 终端B操作
-- 终端A崩溃后,数据自动回滚,只剩张三的记录,李四的记录消失
select * from account;

结论 :只要执行begin手动开启事务,无论autocommit是开启还是关闭,都必须手动commit才会生效,未提交的异常退出会自动回滚。

实验 4:单条 SQL 与自动提交的关系

sql 复制代码
-- 实验一:关闭autocommit,单条SQL未commit,崩溃后回滚
-- 终端A
set autocommit=0;
insert into account values (2, '李四', 10000);
select * from account; -- 能看到数据
-- 异常退出终端
-- 终端B查询,数据回滚消失

-- 实验二:开启autocommit,单条SQL自动提交,崩溃后不回滚
-- 终端A
set autocommit=1;
insert into account values (2, '李四', 10000);
select * from account; -- 能看到数据
-- 异常退出终端
-- 终端B查询,数据依然存在,已持久化

结论autocommit=ON时,InnoDB 会把每一条单 SQL 都封装成事务自动提交;autocommit=OFF时,所有 SQL 都需要手动 commit 才会生效。

6.4 事务操作注意事项

  • 如果没有设置保存点,也可以执行rollback,只能回滚到事务开启前的状态,前提是事务还没有提交;
  • 事务一旦执行commit提交,就不能再执行rollback回滚了;
  • 可以选择回滚到任意一个已创建的保存点;
  • 只有 InnoDB 引擎支持事务和保存点,MyISAM 不支持;
  • 开启事务推荐使用beginstart transaction,语义更清晰。













七. 事务隔离级别:解决并发读写的核心

隔离性是 ACID 中最复杂的特性,MySQL 通过 4 种隔离级别,让我们可以在「数据安全性」和「并发性能」之间做平衡。

7.1 并发事务带来的 3 个核心问题

在讲解隔离级别之前,我们先搞懂并发事务会带来的 3 个经典问题,这也是面试必考点:

问题名称 定义说明
脏读 一个事务读到了另一个事务未提交的修改数据。如果另一个事务回滚,读到的数据就是无效的脏数据。
不可重复读 同一个事务内,多次执行相同的 select 语句,读到的结果不一样。核心原因是其他事务对数据做了修改 / 删除并提交。
幻读 同一个事务内,多次执行相同的 select 语句,第二次读到了第一次没有的新增记录,就像出现了幻觉。核心原因是其他事务做了新增并提交。

7.2 MySQL 的 4 种隔离级别

MySQL 提供了 4 种隔离级别,从低到高分别是:读未提交(read uncommitted)、读已提交(read committed)、可重复读(repeatable read)、串行化(serializable)。

其中,可重复读(repeatable read,简称 RR)是 MySQL 的默认隔离级别。

4 种隔离级别对问题的解决能力

隔离级别 脏读 不可重复读 幻读 加锁读
读未提交(read uncommitted) 会发生 会发生 会发生 不加锁
读已提交(read committed) 不会发生 会发生 会发生 不加锁
可重复读(repeatable read) 不会发生 不会发生 不会发生(MySQL 通过 Next-Key 锁解决) 不加锁
串行化(serializable) 不会发生 不会发生 不会发生 全加锁

7.3 隔离级别的查看与设置

sql 复制代码
-- 查看全局隔离级别
select @@global.tx_isolation;

-- 查看当前会话的隔离级别
select @@session.tx_isolation;
select @@tx_isolation; -- 默认查看当前会话

-- 设置当前会话的隔离级别
set session transaction isolation level 隔离级别名称;

-- 设置全局的隔离级别(重启客户端后生效)
set global transaction isolation level 隔离级别名称;

-- 示例:设置全局隔离级别为读未提交
set global transaction isolation level read uncommitted;

-- 示例:设置当前会话隔离级别为串行化
set session transaction isolation level serializable;


7.4 4 种隔离级别实操演示

所有演示均基于account表,初始数据:

sql 复制代码
truncate table account;
insert into account values (1, '张三', 100.00), (2, '李四', 10000.00);

读未提交(read uncommitted)

这是最低的隔离级别,几乎没有隔离性,会出现脏读,生产环境严禁使用

sql 复制代码
-- 先设置全局隔离级别为读未提交,重启两个终端生效
set global transaction isolation level read uncommitted;

-- 终端A:开启事务,更新数据,不提交
begin;
update account set blance=123.00 where id=1;
-- 不执行commit

-- 终端B:开启事务,查询数据
begin;
select * from account; -- 直接读到了终端A未提交的123.00,出现脏读

核心问题:脏读,读到了其他事务未提交的数据,一旦其他事务回滚,数据就失效了。


读已提交(read committed)

这是大多数数据库(Oracle、SQL Server)的默认隔离级别,解决了脏读,但会出现不可重复读。

sql 复制代码
-- 设置全局隔离级别为读已提交,重启两个终端生效
set global transaction isolation level read committed;

-- 终端A:开启事务
begin;
update account set blance=321.00 where id=1;
-- 此时不commit

-- 终端B:开启事务,第一次查询
begin;
select * from account where id=1; -- 读到的是100.00,没有脏读

-- 终端A:提交事务
commit;

-- 终端B:同一个事务内,第二次查询
select * from account where id=1; -- 读到的是321.00,同一个事务内两次查询结果不同,出现不可重复读

核心问题:不可重复读,同一个事务内,相同的查询语句,两次读到的结果不一样。


可重复读(repeatable read)

MySQL 默认隔离级别,解决了脏读和不可重复读,同时通过 Next-Key 锁(间隙锁 + 行锁)解决了幻读问题。

sql 复制代码
-- 设置全局隔离级别为可重复读,重启两个终端生效
set global transaction isolation level repeatable read;

-- 终端A:开启事务
begin;
update account set blance=4321.00 where id=1;
-- 不commit

-- 终端B:开启事务,第一次查询
begin;
select * from account where id=1; -- 读到100.00

-- 终端A:commit提交事务

-- 终端B:同一个事务内,第二次查询
select * from account where id=1; -- 还是读到100.00,解决了不可重复读

-- 终端B:提交事务后,再次查询,才会读到4321.00
commit;
select * from account where id=1;

幻读验证:

sql 复制代码
-- 终端A:开启事务
begin;
insert into account values (3, '王五', 5432.00);
-- 不commit

-- 终端B:开启事务,第一次查询
begin;
select * from account; -- 只有2条记录,没有王五

-- 终端A:commit提交

-- 终端B:同一个事务内,第二次查询
select * from account; -- 还是只有2条记录,没有读到新增的王五,解决了幻读

-- 终端B:提交事务后,才会读到3条记录
commit;
select * from account;






串行化(serializable)

最高的隔离级别,强制所有事务串行执行,完全解决了脏读、不可重复读、幻读,但并发性能极差,生产环境几乎不使用。

sql 复制代码
-- 设置全局隔离级别为串行化,重启两个终端生效
set global transaction isolation level serializable;

-- 终端A:开启事务,执行查询
begin;
select * from account;

-- 终端B:开启事务,执行更新
begin;
update account set blance=1.00 where id=1; -- 直接被阻塞,直到终端A的事务提交/回滚

-- 终端A:commit提交事务
commit;
-- 终端B的update才会执行

核心特点:所有读写操作都会加锁,事务只能串行执行,安全性最高,但并发性能最低。




八. 一致性:事务的最终归宿

我们前面讲了原子性、隔离性、持久性,最终都是为了保证一致性

一致性分为两个层面:

  • 数据库层面的一致性:事务执行前后,数据库的完整性约束(主键、外键、唯一索引、非空约束等)不会被破坏,比如主键不会重复、库存不会出现负数。
  • 业务层面的一致性:这是由我们的业务代码决定的,比如转账前后两个账户的总金额不变、订单创建后库存必须扣减、用户下单后优惠券必须标记为已使用。

MySQL 的事务机制,为我们提供了保证一致性的技术基础(原子性、隔离性、持久性),但最终的业务一致性,还是需要我们的业务代码来保证。一句话总结:通过 AID(原子性、隔离性、持久性)来保证 C(一致性)。


九. 进阶原理:MVCC 多版本并发控制

很多同学会好奇:MySQL 的可重复读隔离级别,没有加锁,为什么还能解决不可重复读和幻读?为什么多个事务同时读写数据,不会互相阻塞?

答案就是:MVCC(Multi-Version Concurrency Control)多版本并发控制,它是 MySQL 用来解决「读 - 写冲突」的无锁并发控制机制,也是 InnoDB 高性能的核心。

9.1 MVCC 解决了什么问题?

数据库的并发场景分为三种:

  • 读 - 读:不存在任何问题,不需要并发控制;
  • 写 - 写:有线程安全问题,需要加锁控制,会出现更新丢失;
  • 读 - 写:有线程安全问题,会出现脏读、不可重复读、幻读,传统方案是加锁,会导致性能大幅下降。

而 MVCC 就是为了解决「读 - 写冲突」,实现了:读操作不会阻塞写操作,写操作也不会阻塞读操作,大幅提升了数据库的并发读写性能,同时解决了脏读、不可重复读、幻读等隔离性问题。

9.2 MVCC 的三大前置知识

要理解 MVCC,必须先搞懂三个核心基础:3 个隐藏字段、undo 日志、Read View。

InnoDB 行记录的 3 个隐藏字段

我们创建的每一行记录,除了我们定义的字段,InnoDB 会自动添加 3 个隐藏字段:

隐藏字段名 长度 作用说明
DB_TRX_ID 6 字节 最近一次修改(插入 / 更新)这条记录的事务 ID,事务 ID 是单向递增的,事务开启时分配。
DB_ROLL_PTR 7 字节 回滚指针,指向这条记录的上一个历史版本,所有历史版本都保存在 undo 日志中,通过这个指针形成版本链。
DB_ROW_ID 6 字节 隐藏的自增主键,如果我们的表没有定义主键,InnoDB 会自动以这个字段生成聚簇索引;如果表有主键,这个字段就不会存在。

除此之外,还有一个隐藏的删除标记字段:记录被更新或删除时,并不是真的从磁盘删除,而是把这个标记置为删除,后续由 purge 线程清理。


undo 日志

undo 日志简单理解就是MySQL 的历史数据快照缓冲区。

当我们对一条记录执行update/delete时,InnoDB 会先把这条记录的原始数据拷贝到 undo 日志中,然后再修改当前记录,同时把当前记录的DB_ROLL_PTR指向 undo 日志中的历史版本。

多次修改后,undo 日志中就会形成一条基于链表的历史版本链,事务的回滚、MVCC 的快照读,都是基于这个版本链实现的。

举个例子

  • 插入一条记录insert into student (name,age) values ('张三',28);,此时事务 ID 为 10,undo 日志中没有历史版本;
  • 事务 11 执行update student set name='李四' where id=1;,先把原始数据拷贝到 undo 日志,修改当前记录为李四,DB_TRX_ID设为 11,DB_ROLL_PTR指向 undo 日志中的张三版本;
  • 事务 12 执行update student set age=38 where id=1;,再次拷贝当前记录到 undo 日志,修改 age 为 38,DB_TRX_ID设为 12,DB_ROLL_PTR指向李四的版本;
  • 最终形成「张三→李四→最新记录」的版本链。



Read View(读视图)

Read View 是事务执行快照读时,生成的一个读视图,它记录了生成 Read View 的那一刻,MySQL 中当前活跃的(已开启但未提交)事务 ID 列表。

简单理解:Read View 就是事务快照读的那一刻,给数据库的活跃事务拍了一张照片,后续通过这张照片,判断版本链中的哪个数据版本对当前事务是可见的。

Read View 的 4 个核心字段:

字段名 作用说明
m_ids 生成 Read View 时,系统中所有活跃的(未提交)事务 ID 列表
m_up_limit_id 低水位,m_ids中最小的事务 ID,小于这个 ID 的事务,都是已提交的,对当前事务可见
m_low_limit_id 高水位,生成 Read View 时,系统尚未分配的下一个事务 ID,大于等于这个 ID 的事务,对当前事务都不可见
m_creator_trx_id 创建这个 Read View 的当前事务 ID








9.3 版本可见性判断规则

有了版本链和 Read View,MySQL 就能判断哪个历史版本对当前事务是可见的,规则如下(按优先级判断):

  • 如果版本的DB_TRX_ID == m_creator_trx_id:说明是当前事务自己修改的,可见;
  • 如果版本的DB_TRX_ID < m_up_limit_id:说明这个版本的事务在生成 Read View 前就已经提交了,可见;
  • 如果版本的DB_TRX_ID >= m_low_limit_id:说明这个版本的事务是生成 Read View 后才开启的,不可见;
  • 如果版本的DB_TRX_IDm_up_limit_idm_low_limit_id之间:
    • 如果DB_TRX_IDm_ids列表中:说明事务还未提交,不可见;
    • 如果不在m_ids列表中:说明事务已经提交,可见。

如果当前版本不可见,就顺着DB_ROLL_PTR找到下一个历史版本,重复上面的判断,直到找到可见的版本;如果所有版本都不可见,就查询不到这条记录。

9.4 当前读 vs 快照读

MVCC 中,把 select 查询分为两种类型,这是理解 MVCC 的关键:

  • 快照读:读取的是记录的历史版本(快照),不加锁,普通的select * from table就是快照读。MVCC 的无锁并发,就是基于快照读实现的。
  • 当前读:读取的是记录的最新版本,会加锁。insert/update/delete,以及select ... lock in share mode(共享锁)、select ... for update(排他锁),都是当前读。

9.5 RC 与 RR 隔离级别的本质区别

为什么 RC 级别会出现不可重复读,而 RR 级别不会?核心原因就是 Read View 的生成时机不同。

  • 读已提交(RC)级别:事务中,每一次快照读,都会生成一个全新的 Read View。
    • 所以每次 select,都会拿到最新的已提交事务的修改,同一个事务内两次 select,Read View 不一样,读到的结果就不一样,出现不可重复读。
  • 可重复读(RR)级别:事务中,只有第一次快照读,才会生成 Read View,后续所有的快照读,都复用这个 Read View

所以整个事务生命周期内,看到的都是同一个快照,无论其他事务怎么修改、提交,当前事务读到的结果都一致,完美解决了不可重复读,同时也解决了幻读。





十. 总结与面试重点

本文完整覆盖了 MySQL 事务的所有核心知识点,从入门到进阶,这里给大家总结面试和开发中必须掌握的重点:

  • ACID 四大特性:原子性、一致性、隔离性、持久性,必须能清晰解释每个特性的含义;
  • 事务的基础操作begin/commit/rollback/savepoint的使用,autocommit的作用;
  • 并发事务的三个问题:脏读、不可重复读、幻读,必须能说清定义和区别;
  • 四种隔离级别:每个级别能解决什么问题、不能解决什么问题,MySQL 的默认级别是什么;
  • MVCC 底层原理:三个隐藏字段、undo 日志、Read View、可见性规则、RC 与 RR 的本质区别;
  • InnoDB 与 MyISAM 的区别:核心就是 InnoDB 支持事务、行锁、外键,MyISAM 不支持。

事务是 MySQL 开发的基础,也是面试的重中之重,只有吃透了事务的原理,才能写出高并发、高可靠的业务代码,避免线上数据不一致的严重事故。


结尾:

html 复制代码
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:事务是 MySQL 开发的基础,也是面试的重中之重,只有吃透了事务的原理,才能写出高并发、高可靠的业务代码,避免线上数据不一致的严重事故。

✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど

相关推荐
Elastic 中国社区官方博客2 小时前
将 Logstash 管道从 Azure Event Hubs 迁移到 Kafka 输入插件
大数据·数据库·elasticsearch·microsoft·搜索引擎·kafka·azure
浅时光_c2 小时前
9 循环语句
c语言·开发语言
鸽芷咕2 小时前
Oracle 替代工程实践深度解析:金仓全链路工程实践 —— 从评估决策到平滑上线的深度技术攻坚
数据库·oracle
斯普信云原生组2 小时前
Docker 开源软件应急处理方案及操作手册——Docker 服务启动故障处理
运维·docker·容器
不才小强2 小时前
GDB调试工具
linux
paeamecium2 小时前
【PAT甲级真题】- Reversing Linked List (25)
数据结构·c++·算法·pat
.select.2 小时前
TCP 2
服务器·网络·tcp/ip
gushinghsjj2 小时前
元数据管理包含哪些?元数据管理如何支持数据分析?
数据库·oracle·数据分析
TTTrees2 小时前
C++学习笔记(38):封装、继承、多态
c++