MySQL中的事务和MVCC原理是什么

发布时间:2022-03-09 13:54:30 作者:iii
来源:亿速云 阅读:136

这篇“MySQL中的事务和MVCC原理是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“MySQL中的事务和MVCC原理是什么”文章吧。

MySQL中的事务和MVCC原理是什么

01 什么是事务?

数据库事务指的是一组数据操作,事务内的操作要么就是全部成功,要么就是全部失败,什么都不做,其实不是没做,是可能做了一部分但是只要有一步失败,就要回滚所有操作,有点一不做二不休的意思。

在 MySQL 中,事务支持是在引擎层实现的。MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一

1.1 四大特性

1.2 隔离级别

SQL 事务的四大特性中原子性、一致性、持久性都比较好理解。但事务的隔离级别确实比较难的,今天主要聊聊 MySQL 事务的隔离性。

SQL 标准的事务隔离从低到高级别依次是:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。级别越高,效率越低

1.3 解决的并发问题

SQL 事务隔离级别的设计就是为了能最大限度的解决并发问题:

SQL 不同的事务隔离级别能解决的并发问题也不一样,如下表所示:只有串行化的隔离级别解决了全部这 3 个问题,其他的 3 个隔离级别都有缺陷

事务隔离级别脏读不可重复读幻读
读未提交可能可能可能
读已提交不可能可能可能
可重复读不可能不可能可能
串行化不可能不可能不可能

PS:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

1.4 举个栗子

这么说可能有点难以理解,举个栗子。还是之前的表结构以及表数据

CREATE TABLE `student`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `age` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 66 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

MySQL中的事务和MVCC原理是什么

假设现在,我要同时启动两个食物,一个事务 A 查询 id = 2 的学生的 age,一个事务 B 更新 id = 2 的学生的 age。流程如下,在四种隔离级别下的 X1、X2、X3 的值分别是怎样的呢?

MySQL中的事务和MVCC原理是什么

那为什么会出现这样的结果呢?事务隔离级别到底是怎么实现的呢?

事务隔离级别是怎么是实现的呢?我在极客时间丁奇老师的课上找到了答案:

实际上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在 “可重复读” 隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在 “读提交” 隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交” 隔离级别下直接返回记录上的最新值,没有视图概念;而 “串行化” 隔离级别下直接用加锁的方式来避免并行访问

1.5 设置事务隔离级别

不同的数据库默认设置的事务隔离级别也大不一样,Oracle 数据库的默认隔离级别是读提交,而 MySQL 是可重复读。所以,当你的系统需要把数据库从 Oracle 迁移到 MySQL 时,请把级别设置成与搬迁之前的(读提交)一致,避免出现不可预测的问题

1.5.1 查看事务隔离级别

# 查看事务隔离级别
5.7.20 之前
SELECT @@transaction_isolation
show variables like 'transaction_isolation';

# 5.7.20 以及之后
SELECT @@tx_isolation
show variables like 'tx_isolation'

+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+

1.5.2 设置隔离级别

修改隔离级别语句格式是:set [作用域] transaction isolation level [事务隔离级别]

其中作用域可选:SESSION(会话)、GLOBAL(全局);隔离级别就是上面提到的 4 种,不区分大小写。

例如:设置全局隔离级别为读提交

set global transaction isolation level read committed;

1.6 事务的启动

MySQL 的事务启动有以下几种方式:

# 更新学生名字
START TRANSACTION;
update student set name = '张三' where id = 2;
commit;

02 事务隔离的实现

理解了隔离级别,那事务的隔离是怎么实现的呢?要想理解事务隔离,先得了解 MVCC 多版本的并发控制这个概念。而 MVCC 又依赖于 undo log 和 read view 实现。

2.1 什么是 MVCC?

百度上的解释是这样的:

MVCC,全称 Multi-Version Concurrency Control,即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。

MVCC 使得数据库读不会对数据加锁,普通的 SELECT 请求不会加锁,提高了数据库的并发处理能力;数据库写才会加锁。 借助 MVCC,数据库可以实现 READ COMMITTED,REPEATABLE READ 等隔离级别,用户可以查看当前数据的前一个或者前几个历史版本,保证了 ACID 中的 I 特性(隔离性)。

MVCC 只在 REPEATABLE READ 和 READ COMMITIED 两个隔离级别下工作。其他两个隔离级别都和 MVCC 不兼容 ,因为 READ UNCOMMITIED 总是读取最新的数据行,而不是符合当前事务版本的数据行。而 SERIALIZABLE 则会对所有读取的行都加锁。

2.1.1 InnDB 中的 MVCC

InnDB 中每个事务都有一个唯一的事务 ID,记为 transaction_id。它在事务开始时向 InnDB 申请,按照时间先后严格递增。

而每行数据其实都有多个版本,这就依赖 undo log 来实现了。每次事务更新数据就会生成一个新的数据版本,并把  transaction_id 记为 row trx_id。同时旧的数据版本会保留在 undo log 中,而且新的版本会记录旧版本的回滚指针,通过它直接拿到上一个版本。

所以,InnDB 中的 MVCC 其实是通过在每行记录后面保存两个隐藏的列来实现的。一列是事务 ID:trx_id;另一列是回滚指针:roll_pt。

2.2 undo log

回滚日志保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。

根据操作的不同,undo log 分为两种: insert undo log 和 update undo log。

2.2.1 insert undo log

insert 操作产生的 undo log,因为 insert 操作记录没有历史版本只对当前事务本身可见,对于其他事务此记录不可见,所以 insert undo log 可以在事务提交后直接删除而不需要进行 purge 操作。

purge 的主要任务是将数据库中已经 mark del 的数据删除,另外也会批量回收 undo pages

所以,插入数据时。它的初始状态是这样的:

MySQL中的事务和MVCC原理是什么

2.2.2 update undo log

UPDATE 和 DELETE 操作产生的 Undo log 都属于同一类型:update_undo。(update 可以视为 insert 新数据到原位置,delete 旧数据,undo log 暂时保留旧数据)。

事务提交时放到 history list 上,没有事务要用到这些回滚日志,即系统中没有比这个回滚日志更早的版本时,purge 线程将进行最后的删除操作。

一个事务修改当前数据:

MySQL中的事务和MVCC原理是什么

另一个事务修改数据:

MySQL中的事务和MVCC原理是什么

这样的同一条记录在数据库中存在多个版本,就是上面提到的多版本并发控制 MVCC。

另外,借助 undo log 通过回滚可以回到上一个版本状态。比如要回到 V1 只需要顺序执行两次回滚即可。

2.3 read-view

read view 是 InnDB 在实现 MVCC 时用到的一致性读视图,用于支持 RC(读提交)以及 RR(可重复读)隔离级别的实现

read view 不是真实存在的,只是一个概念,undo log 才是它的体现。它主要是通过版本和 undolog 计算出来的。作用是决定事务能看到哪些数据

每个事务或者语句有自己的一致性视图。普通查询语句是一致性读,一致性读会根据 row trx_id 和一致性视图确定数据版本的可见性

2.3.1 数据版本的可见性规则

read view 中主要包含当前系统中还有哪些活跃的读写事务,在实现上 InnDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正活跃(还未提交)的事务

前面说了事务 ID 随时间严格递增的,把系统中已提交的事务 ID 的最大值记为数组的低水位,已创建过的事务 ID + 1记为高水位

这个视图数组和高水位就组成了当前事务的一致性视图(read view)

这个数组画个图,长这样:

MySQL中的事务和MVCC原理是什么

规则如下:

第三点我在看教程的时候也有点疑惑,好在有热心网友解答:

落在绿色区域意味着是事务 ID 在低水位和高水位这个范围里面,而真正是否可见,看绿色区域是否有这个值。如果绿色区域没有这个事务 ID,则可见,如果有,则不可见。在这个范围里面并不意味着这个范围就有这个值,比如 [1,2,3,5],4 在这个数组 1-5 的范围里,却没在这个数组里面。

这样说可能有点难以理解,我假设一个场景:三个事务对同一条数据进行查询更新等操作,为此画了张图以方便理解:

MySQL中的事务和MVCC原理是什么

原始数据还是下图这样的,对 id = 2 的张三进行信息的更新:

MySQL中的事务和MVCC原理是什么

针对上图,我想提个问题。分别在 RC(读提交)以及 RR(可重复读)隔离级别下,T4 和 T5 时间点的查询 age 值分别是多少呢?T4 更新的值又是多少呢?思考片刻,相信大家都有自己的答案。答案在文末,希望大家能带着自己的疑问继续读下去。

2.3.2 RR(可重复读)下的结果

RR 级别下,查询只承认在事务启动前就已经提交完成的数据,一旦启动事务就会建视图。所以使用 start transaction with consistent snapshot 命令,马上就会建视图

现在假设:

在这种隔离级别下,他们创建视图的时刻如下:

MySQL中的事务和MVCC原理是什么

根据上图得,事务 A 的视图数组是[2,3];事务 B 的视图数组是 [2,3,4];事务 C 的视图数组是[2,3,4,5]。分析一波:

这样执行下来,虽然期间这一行数据被修改过,但是事务 A 不论在什么时候查询,看到这行数据的结果都是一致的,所以我们称之为一致性读

其实视图是否可见主要看创建视图和提交的时机,总结下规律:

2.3.2.1 快照读和当前读

事务 B 的 update 语句,如果按照上图的一致性读,好像结果不大对?

如下图周明,B 的视图数组是先生成的,之后事务 C 才提交。那就应该看不见 C 修改的 age = 23 呀?最后 B 怎么得出 24 了?

MySQL中的事务和MVCC原理是什么

没错,如果 B 在更新之前执行查询语句,那返回的结果肯定是 age = 22。问题是更新就不能在历史版本更新了呀,否则 C 的更新不就丢失了?

所以,更新有个规则:更新数据都是先读后写(读是更新语句执行,不是我们手动执行),读的就是当前版本的值,叫当前读;而我们普通的查询语句就叫快照读

因此,在更新时,当前读读到的是 age = 23,更新之后就成 24 啦。

2.3.2.2 select 当前读

除了更新语句,查询语句如果加锁也是当前读。如果把事务 A 的查询语句 select age from t where id = 2 改一下,加上锁(lock in mode 或者 for update),也都可以得到当前版本 4 返回的 age = 24

下面就是加了锁的 select 语句:

select age from t where id = 2 lock in mode;
 select age from t where id = 2 for update;
2.3.2.3 事务 C 不马上提交

假设事务 C 不马上提交,但是 age = 23 版本已生成。事务 B 的更新将会怎么走呢?

MySQL中的事务和MVCC原理是什么

事务 C 还没提交,写锁还没释放,但是事务 B 的更新必须要当前读且必须加锁。所以事务 B 就阻塞了,必须等到事务 C 提交,释放锁才能继续当前的读。

MySQL中的事务和MVCC原理是什么

2.3.3 RC(读提交)下的结果

在读提交隔离级别下,查询只承认在语句启动前就已经提交完成的数据;每一个语句执行之前都会重新算出一个新的视图

注意:在上图的表格中用于启动事务的是 start transaction with consistent snapshot 命令,它会创建一个持续整个事务的视图。所以,在  RC 级别下,这命令其实不起作用。等效于普通的 start transaction(在执行 sql 语句之前才算是启动了事务)。所以,事务 B 的更新其实是在事务 C 之后的,它还没真正启动事务,而 C 已提交

现在假设:

在这种隔离级别下,他们创建视图的时刻如下:

MySQL中的事务和MVCC原理是什么

根据上图得,事务 A 的视图数组是[2,3,4],但它的高水位是 6或者更大(已创建事务 ID + 1);事务 B 的视图数组是 [2,4];事务 C 的视图数组是 [2,5]。分析一波:

以上就是关于“MySQL中的事务和MVCC原理是什么”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。

推荐阅读:
  1. PostgreSQL、Oracle/MySQL和SQL Server的MVCC实现原理方式
  2. 怎么理解MySQL中的MVCC

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

mysql mvcc

上一篇:bootstrap与layui可以一起用吗

下一篇:php可以写区块链吗

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》