一文搞懂MySQL事务的隔离性如何实现|MVCC

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: MySQL有ACID四大特性,本文着重讲解MySQL不同事务之间的隔离性的概念,以及MySQL如何实现隔离性。下面先罗列一下MySQL的四种事务隔离级别,以及不同隔离级别可能会存在的问题。
关注公众号【程序员白泽】,带你走进一个不一样的程序员/学生党

前言

MySQL有ACID四大特性,本文着重讲解MySQL不同事务之间的隔离性的概念,以及MySQL如何实现隔离性。下面先罗列一下MySQL的四种事务隔离级别,以及不同隔离级别可能会存在的问题。事务隔离级别越高,多个事务在并发访问数据库时互相产生数据干扰的可能性越低,但是并发访问的性能就越差。(相当于牺牲了一定的性能去保证数据的安全性)

下面这张表,展示了MySQL的四大隔离级别和伴随着的一些问题,下面详细介绍。

image-20220411111938966

事务隔离级别

读未提交:多个事务同时修改一条记录,A事务对其的改动在A事务还没提交时,在B事务中就可以看到A事务对其的改动。

读已提交:多个事务同时修改一条记录,A事务对其的改动在A事务提交之后,在B事务中可以看到A事务对其的改动。

可重复读:多个事务同时修改一条记录,这条记录在A事务执行期间是不变的(别的事务对这条记录的修改不被A事务感知)。

串行化:多个事务同时访问一条记录(CRUD),读加读锁,写加写锁,完全退化成了串行的访问,自然不会收到任何其他事务的干扰,性能最低。

不同级别伴随的问题

脏读:A事务在提交前对一个字段的改动会被B事务感知,那么事务之间就很容易产生干扰,假如A对一个字段改动之后被B感知,但是A又回滚了事务,则对该字段的改动依旧保留在B的查询结果中,那么这样的数据就是脏数据(处于处理中间过程的数据)。

不可重复读:A事务对于一条记录的读取结果,在B事务对其修改并提交之后,A再次读取同一条记录会得到不同的结果。

幻读:侧重于A事务的同一个范围查询命令,前后两次得到不同的记录数量,原因是B事务可能对其进行了插入。

小结一下

通过阅读上面给出的内容,可以得到结论:

  1. 读未提交隔离级别并没有对行数据的可见性做任何限制,所有事务之间的改动都是互相可见的,所以存在很多问题,不推荐使用;
  1. 串行化隔离级别因为通过锁机制对记录的访问进行限制,所以安全性最高,但并发访问退化成串行访问,性能较低;

因此本文将侧重于探究MySQL如何实现读已提交可重复读两种隔离级别(也就是你听闻的MVCC多版本并发控制的实现),通过后面的学习你将理解读已提交隔离级别如何解决脏读可重复读隔离级别如何更进一步解决不可重复读

接下来我将向你介绍undo 版本链机制以及read view快照读机制,这两个机制相互配合是实现MVCC的核心,而读已提交可重复读隔离级别的实现都是建立在这两个核心机制之上。

undo 版本链

undo 版本链就是指undo log的存储在逻辑上的表现形式,它被用于事务当中的回滚操作以及实现MVCC,这里介绍一下undo log之所以能实现回滚记录的原理。

对于每一行记录,会有两个隐藏字段:row_trx_idroll_pointerrow_trx_id表示更新(改动)本条记录的全局事务id (每个事务创建都会分配id,全局递增,因此事务id区别对某条记录的修改是由哪个事务作出的)roll_pointer是回滚指针,指向当前记录的前一个undo log版本,如果是第一个版本则roll_pointer指向nil,这样如果有多个事务对同一条记录进行了多次改动,则会在undo log中以链的形式存储改动过程。

假如有两个事务AB,数据表中有一行id为1的记录,其字段a初始值为0,事务A对id=1的行的a修改为1,事务B对id=1的行的a字段修改为2,则undo log版本链记录如下:

image-20220410185551723

在上图中,最下方的undo log中记录了当前行的最新版本,而该条记录之前的版本则以版本链的形式可追溯,这也是事务回滚所做的事。那undo log版本链和事务的隔离性有什么关系呢?那就要引入另一个核心机制:read view。

read view

read view表示快照读,这个快照读会记录四个关键的属性:

  1. create_trx_id: 当前事务的id
  2. m_idx: 当前正在活跃的所有事务id(id数组),没有提交的事务的id
  3. min_trx_id: 当前系统中活跃的事务的id最小值
  4. max_trx_id: 当前系统中已经创建过的最新事务(id最大)的id+1的值

当一个事务读取某条记录时会追溯undo log版本链,找到第一个可以访问的版本,而该记录的某一个版本是否能被这个事务读取到遵循如下规则:(这个规则永远成立,这个需要好好理解,对后面讲解可重复读和读已提交两个级别的实现密切相关)

  1. 如果当前记录行的row_trx_id小于min_trx_id,表示该版本的记录在当前事务开启之前创建,因此可以访问到
  2. 如果当前记录行的row_trx_id大于等于max_trx_id,表示该版本的记录创建晚于当前活跃的事务,因此不能访问到
  3. 如果当前记录行的row_trx_id大于等于min_trx_id且小于max_trx_id,则要分两种情况:

    • 当前记录行的row_trx_id在m_idx数组中,则当前事务无法访问到这个版本的记录 (除非这个版本的row_trx_id等于当前事务本身的trx_id,本事务当然能访问自己修改的记录) ,在m_idx数组中又不是当前事务自己创建的undo版本,表示是并发访问的其他事务对这条记录的修改的结果,则不能访问到。
    • 当前记录行的row_trx_id不在m_idx数组中,则表示这个版本是当前事务开启之前,其他事务已经提交了的undo版本,当前事务可访问到。

配合使用read viewundo log版本链就能实现事务之间并发访问相同记录时,可以根据事务id不同,获取同一行的不同undo log版本(多版本并发控制)。下面通过模拟并发访问的两个事务操作,介绍MVCC的实现(具体来说就是可重复读读已提交两个隔离级别的实现)

可重复读

下面模拟两个并发访问同一条记录的事务AB的行为,假设这条记录初始时id=1,a=0,该记录两个隐藏字段row_trx_id = 100,roll_pointer = nil

注意:在可重复读隔离级别下,当事务sql执行的时候,会生成一个read view快照,且在本事务周期内一直使用这个read view,下面给出了并发访问同一条记录的两个事务AB的具体执行过程,并解释可重复读是如何实现的(解决了脏读不可重复读)。

image-20220411112017070

事务A的read view:

create_trx_id = 101| m_idx = [101, 102]|min_trx_id = 101|max_trx_id = 103

事务B的read view:

create_trx_id = 102| m_idx = [101, 102]|min_trx_id = 101|max_trx_id = 103

(ps. 这里因为AB事务是并发执行,因此两个事务创建的read view的max_trx_id = 103)

image-20220410205705912

这里要注意的是,每次对一条记录发生修改,就会记录一个undo log的版本,则在A事务中第二次查询id=1的记录的a的值的时候,B事务对该记录的修改已经添加到版本链上了,此时这个undo logtrx_id = 102,在A事务的read viewm_idx数组中且不等于A事务的trx_id = 101,因此无法访问到,需要在向前回溯,这里找到trx_id = 100的记录版本(小于A事务read viewmin_trx_id属性,因此可以访问到),故A事务第二次查询依旧得到a = 0,而不是B事务修改的a = 1。

你可能有疑问,在A事务第二次查询的时候,B事务已经完成提交了,那么A事务的read view的m_idx数组应该移除102才对啊,它存的不是当前活跃的事务的id吗?·

注意:在可重复读隔离级别下,当事务sql执行的时候,会生成一个read view快照,且在本事务周期内一直使用这个read view,虽然102确实应该从A事务的read view中移除,但是因为read view在可重复读隔离级别下只会在第一条SQL执行时创建一次,并始终保持不变直到事务结束。

那么也就明白了,在可重复读隔离级别下,因为read view只在第一条SQL执行时创建,因此并发访问的其他事务提交前改动的脏数据、以及并发访问的其他事务提交的改动数据都对当前事务是透明的(尽管确实是记录在了undo log版本链中) ,这就解决了脏读和不可重复读(即使其他事务提交的修改,对A事务来说前后查询结果相同)的问题!

读已提交

还是借助上面事务处理的例子,所有的事务处理流程不变,只是将隔离级别调整为读已提交,读已提交依旧遵守read view和undo log版本链机制,它和可重复读级别的区别在于,每次执行sql,都会创建一个read view,获取最新的事务快照。 而因为这个区别,读已提交产生了不可重复读的问题,下面来分析一下原因:

image-20220411112017070

事务A第一次查询创建的read view:

create_trx_id = 101| m_idx = [101, 102]|min_trx_id = 101|max_trx_id = 103

事务B的read view:

create_trx_id = 102| m_idx = [101, 102]|min_trx_id = 101|max_trx_id = 103

事务A第二次查询创建的read view:

create_trx_id = 101| m_idx = [101]|min_trx_id = 101|max_trx_id = 103

(ps. 这里因为AB事务是并发执行,因此两个事务创建的read view的max_trx_id = 103)

image-20220410205705912

这里重点观察A事务的第二次查询,之前你可能就意识到了,在事务B完成提交后,当前系统中活跃的事务id应该移除102,但是因为在可重复读隔离级别下,A事务的read view只会在第一个SQL执行时创建,而在读已提交隔离级别下,每次执行SQL都会创建最新的read view,且此时 m_idx数组中移除了102,那么事务A在追溯undo log版本链的时候,最新版本记录的trx_id = 102,102不在A事务的m_idx数组中,且101 = min_trx_id <= 102 < max_trx_id = 103,因此可以访问到B事务的提交结果。

那么对A事务来说,在事务过程中读取同一条记录第一次得到a=0,第二次得到a=1,所以出现了不可重复读的问题(这里B不提交的话A如果就进行了第二次查询,则102不会从A事务的read view移除,则A事务依旧访问不到B事务未提交的修改,因此脏读还是可以避免的!)

结束语

在我的理解中,MVCC多版本并发控制的实现可以理解成读已提交、可重复读两种隔离级别的实现,通过控制read view的创建时机(其访问机制是不变的),配合undo log版本链可以实现事务之间对同一条记录的并发访问,并获得不同的结果。

关注公众号【程序员白泽】,带你走进一个不一样的程序员/学生党,公众号回复【简历】可以获得我正在使用的简历模板,平时也会同步更新文章。希望大家都能收获心仪的offer~
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
存储 关系型数据库 MySQL
MySQL MVCC全面解读:掌握并发控制的核心机制
【10月更文挑战第15天】 在数据库管理系统中,MySQL的InnoDB存储引擎采用了一种称为MVCC(Multi-Version Concurrency Control,多版本并发控制)的技术来处理事务的并发访问。MVCC不仅提高了数据库的并发性能,还保证了事务的隔离性。本文将深入探讨MySQL中的MVCC机制,为你在面试中遇到的相关问题提供全面的解答。
106 2
|
2月前
|
存储 SQL 关系型数据库
MySQL的事务隔离级别
【10月更文挑战第17天】MySQL的事务隔离级别
103 43
|
1月前
|
存储 关系型数据库 MySQL
MySQL MVCC深度解析:掌握并发控制的艺术
【10月更文挑战第23天】 在数据库领域,MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种重要的并发控制机制,它允许多个事务并发执行而不产生冲突。MySQL作为广泛使用的数据库系统,其InnoDB存储引擎就采用了MVCC来处理事务。本文将深入探讨MySQL中的MVCC机制,帮助你在面试中自信应对相关问题。
98 3
|
2月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1650 14
|
3月前
|
存储 Oracle 关系型数据库
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
从基本特性、技术选型、字段类型、事务提交方式、SQL语句、分页方法等方面对比Oracle和MySQL的区别。
566 18
Oracle和MySQL有哪些区别?从基本特性、技术选型、字段类型、事务、语句等角度详细对比Oracle和MySQL
|
2月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
3月前
|
关系型数据库 MySQL 数据库
MySQL高级篇——MVCC多版本并发控制
什么是MVCC、快照读与当前读、隐藏字段、Undo Log版本链、ReadView、举例说明、InnoDB 解决幻读问题
MySQL高级篇——MVCC多版本并发控制
|
3月前
|
SQL 关系型数据库 MySQL
MySQL基础:事务
本文详细介绍了数据库事务的概念及操作,包括事务的定义、开启、提交与回滚。事务作为一组不可分割的操作集合,确保了数据的一致性和完整性。文章还探讨了事务的四大特性(原子性、一致性、隔离性、持久性),并分析了并发事务可能引发的问题及其解决方案,如脏读、不可重复读和幻读。最后,详细讲解了不同事务隔离级别的特点和应用场景。
150 4
MySQL基础:事务
|
2月前
|
SQL 关系型数据库 MySQL
【MySQL】索引和事务
【MySQL】索引和事务
55 0
|
3月前
|
SQL Oracle 关系型数据库
详解 MySQL 的事务以及隔离级别
详解 MySQL 的事务以及隔离级别
44 0