事务隔离级别与MVCC (1)—mysql进阶(六十七)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 事务隔离级别与MVCC (1)—mysql进阶(六十七)

前面我们说了undo日志写入undo页面链表时,先需要把undo page header、undo segment header、undo log header等。每个事务都会有相应的undo链表,如果只存储一点数据不是很浪费吗,于是有了可重用,满足当前链表只有一个页,并且小于总空间的3/4。还介绍了回滚段,默认128个回滚段,每个段有1024个undo slot,每个slot分配给不同的事务,对应一个单独的undo页面链表。Undo日志也会记录redo日志,但临时表的undo日志写入不会记录redo日志,他的记录过程是先修改了数据,则会在系统表空间申请一个rollback segment header页面地址,循环获取,从第0号,第33~127号。分配了回滚段后,在段里查看cache是否存在undo slot,不存在就去rollback_segment_header找到一个undo slot分配该事务,如果没找到,则需要去undo log segment申请一个first undo page

重用的undo日志 (3)—mysql进阶(六十六)


我们先创建一个表

mysql> create table hero(
   -> number int,
   -> name varchar(100),
   -> country varchar(100),
   -> primary key(number)
   -> )engine=innodb charset=utf8;
Query OK, 0 rows affected (0.06 sec)

INSERT INTO hero VALUES(1, '刘备', '蜀');


事务隔离级别


我们知道mysql是客户端和服务端架构的软件,对于同一个服务器,有若干个客户端与之连接,每个连接上之后,可以称为【session】。每次客户端会发送一个请求,或者一个事务给服务端,但如果用阻塞的方式,那就性能太慢,为了提高效率而保证每个事务之间都有隔离性,鱼和熊掌不可兼得,舍弃一部分隔离性取来提高性能。


事务并发执行遇到的问题

我们先看看在几个事务同时并发运行时候可能遇到的问题:


脏写(dirty write)

如果一个事务修改了另一个未提交的事务,这就发生了脏写。


场景:trx1和trx2,两个事务修改了同一条记录,但trx1还没提交,trx2把他回滚了,这时候trx1就么有效果。


导致的结果:trx1明明修改了数据,并且commit但是什么都没变化。


脏读(dirty read)

如果一个事物修改后,还未提交或者准备回滚,但是其他事务读了这条未提交的数据,就是脏读。


场景:trx1读,trx2修改了hero成关羽,但是最后准备回滚,在回滚之前被并发的trx1读出来是关羽。


导致的结果:读出来是关羽。


不可重复读(Non-Repeatable Read)

当在同一个事务里,一条记录在其他事物被更改,导致多次查询出来的事务不一致,这种现象就是不可重复读。


场景:trx1读两次,trx2修改多次。


导致的结果:每次读取的数据都是不一致的。


幻读(Phantom)

幻读意味着insert,多次查询的数据,不一致,并且增多了。


场景:trx1读取两次,第一次读取了一条记录,第二次读取了两条记录,trx2事务insert了新纪录到表里。


导致的结果:后面读取的数据比前面读取的数据多。


注意:明确规定,删除或者修改都不算幻读,只有发生insert,多读了数据。


Sql标准中的四种隔离级别

综上所述,脏写>脏读>不可重复读>幻读


为了解决这些问题,于是mysql设计了四种隔离级别:


Read uncommit:未提交读。可能发生脏读,不可重复读,幻读。


Read commit:提交读。只发生不可重复读,幻读。


Repeatable read:可重复读。只发生幻读。


Serializable:可串行化。全部不会发生。


为啥没有脏写,因为脏写问题太严重了,任何情况下都不予许发生。(你想在你修改数据的时候,其他事物帮你吧数据回滚。。)


不同的数据库厂商对sql标准不同,比如oracle就只支持read committed和serializable。Mysql默认是repeatable read,但是可以禁止幻读发生。(后面会说如何禁止)


如何设置mysql隔离级别

mysql> set transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)


后面的level参数可以写入四个隔离级别


这种只对下一个transaction有效,当下一个transaction结束,则恢复到之前的隔离级别。并且该语句不能再事务执行期间执行,否则会报错。


当我们给transaction前面加一个关键字的时候:


使用GLOBAL关键字(在全局范围影响):


比方说 set global transaction isolation level read committed;


则只对执行完该语句之后产生的会话起作用,当前已存在的会话无效。


使用Session关键字(在会话范围影响):


比方说 set session transaction isolation level read committed;


则对当前会话所有后续事务有效。


该语句可以在已开启事务中间执行,但不会影响正在执行的事务。


如果在事务之间执行,则对后续事务有效。


如果我们在服务器启动时想改变默认隔离级别,可以修改启动参数transaction-isolation的值,比方我们在启动mysql的时候加上

--transaction-isolation=serializable,那么事务的隔离级别就变成了serializable。

查看的话我们可以通过show variables like ‘transaction_isolation’;


MVCC原理


版本链

我们前面说过innoDB包含两个必要的隐藏链,一个是trx_id和roll_pointer(row_id不是必须的,当没有主见和唯一索引才会创建。)


Trx_id:每次一个事务对某个聚簇索引记录进行更改,都会吧事务的id赋值给trax_id。就是属于某个事务的id,回滚对应的每个事务是独立且互相隔离,每个事物都有四个undo页面链表,临时表和普通表,insert undo 和update undo。


Roll Pointer:每次对页面改动的时候,会吧旧的日志记录到undo日志,这个指针可以找到他。指向回滚的页面,如果指向的是delete页面,delete有一个old roll pointer会指向上一个执行的sql,也就是insert 的undo页面。

比如我们在事务id为80的事务里插入一条sql,


mysql> SELECT * FROM hero;
+--------+--------+---------+
| number | name   | country |
+--------+--------+---------+
|      1 | 刘备   | 蜀      |
+--------+--------+---------+
1 row in set (0.07 sec)

这时候这个数据结构就是:

Number:1

Name:刘备

Country:蜀

Trx_id:80

Roll_Pointer:指向inser undo页面。


(注意:实际上insert undo只有在事务未提交前起作用,当事务提交后,就没用了,它占用的undo log segment也会被系统收回,也就是undo日志占用 的undo页面链表要么被重用,要么被释放)。虽然insert undo占用的日志被释放,但是roll_pointer的值并不会被清除,roll_pointer属性占用7个字节,第一个比特位就是指向undo日志类型,如果该比特位值为1,那么代表它指向undo日志类型为insert undo。


当trx100和trx200两个事务同时并行修改一条数据会发生什么呢?

trx100:Begin。
trx200:begin。
trx100:update hero set name = ‘关羽’where number = ‘1’。
trx100:update hero set name = ‘张飞’where number = ‘1’。
trx100:commit。
trx200:update hero set name = ‘赵云’where number = ‘1’。
trx200:update hero set name = ‘诸葛亮’where number = ‘1’。
trx200:commit。

这时候交叉更新同一条记录不是会发生脏数据吗,后面会提到mysql的行锁。


每次对数进行update修改,都会记录一条undo日志,每条undo日志都会对应一个roll_pointer属性,可以吧上面这些数据串联起来,串联起来的头部第一条数据就是页面的真实数据,每次修改都会吧修改的数据放入undo页面。这个链表我们就称他为【版本链】,每个版本链还对应着事务id。


ReadView


对于使用read uncommitted隔离级别的事务来说,由于可以读取未提交的修改数据,所以直接读最新的数据就好。对于serializable隔离级别的,mysql选择用锁的方式来访问记录。对于read committed和repeatable read隔离级别的事务,都必须保证已经提交的事务修改过的记录,也就是另一个事务还未提交,其他是不能读取最新的数据。核心问题就是:判断下版本链中,哪个版本是对当前事务可见的。于是innoDB设计出readView的概念,这里面有四个比较重要的内容:


M_ids:表示生在readView时当前系统中活跃的读写事务的事务id列表。


Min_trx_id:表示在生成readView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。


Max_trx_id:表示生成readView时系统应该分配给下一个事务的id值。


(注意:max_trx_id并不是m_ids里的最大值,比如m_ids里有三个事务,1,2,3,当事务3提交了,m_ids只有1,2两个事务,那么新的事务在生成readView时候max_trx_id就是4)


Creator_trx_id:表示生成该readView的事务id。


(前面说过只有再给表做改动时候才有事务id,select没有,如果不存在事务id,creator_trx_id为0)


有了这个readView,这样在访问某条记录时,只要按下面步骤判断记录的某个版本是否可见:


如果被访问的版本trx_id值与readView中的creator_trx_id值相同,意味着当前事务在访问他字节修改过的记录,所以该版本可以在当前事务访问。

如果被访问的版本trx_id值小于 readView中的creator_trx_id值,表名生成该版本的事务在当前事务生成readView前已经提交,所以该版本可以被当前事务访问。

如果被访问的版本trx_id值大于readView中的creator_trx_id值,表名生成该版本的事务在当前事务生成readView后才开启,所以该版本不可以被当前事务访问。

如果被访问的版本trx_id值在readView的max_trx_id和min_trx_id之间,那么就需要判断一下trx_id是否在m_ids列表,如果在,说明创建readView时生成该版本事务还是活跃的,该版本不可以被访问。如果不在,说明创建readView时生成该版本的事务已经被提交,该版本可以被访问。

如果某个版本的数据对当前事务不可见的话,就会顺着版本找到下一个版本的数据,继续按照上面的步骤判断可见性,以此类推,直到找到最后一个版本为止。如果最后一个版本也不可见,则意味着该条记录读当前事务不可见。


在mysql中,read committed和repeatable read非常大的区别就是生成read view的时机不同。我们以hero表为例,假如表里有一条事务id为80的事务插入一条数据。


mysql> SELECT * FROM hero;
+--------+--------+---------+
| number | name   | country |
+--------+--------+---------+
|      1 | 刘备   | 蜀      |
+--------+--------+---------+
1 row in set (0.07 sec)


Reac committed每次读取数据前生成一条readView

比方说两个事务id分别为100、200的事务在执行:


# Transaction 100
BEGIN;
UPDATE hero SET name = '关羽' WHERE number = 1;
UPDATE hero SET name = '张飞' WHERE number = 1;
# Transaction 200
BEGIN;
# 更新了一些别的表的记录
...

(注意:为什么这里事务200要更新别的,因为事务只有在执行delete,update,insert的时候才会分配事务id,这个id是自增的,所以我们目的是为了让他分配事务id)


此刻hero表的number为1的版本链如下:


1,张飞,蜀,100,roll_pointer

1,关羽,蜀,100,roll_pointer

1,刘备,蜀,80,roll_pointer

这里面1是页面中的记录,2,3是undo日志,一起就组成了版本链。

假设现在使用repatable read事务开始执行:


# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 100、200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'


这个select的执行过程如下:


在执行select语句时会生成一个read View,readView的m_ids列表的内容是【100,200】,min_trx_id为100,max_trx_id为201,creator_trx_id为0。


然后从版本链中挑选可见的记录,从上可以看到,版本链可见的是‘张飞’,该版本的trx_id为100,在m_ids内,所以不符合可见性,根据roll_pointer跳到下一个版本。


下一个版本的name是‘关羽‘,该版本的trx_id也是100,也在m_ids列表内,所以也不符合可见性,跳到下一个版本。


下一个版本的name是‘刘备‘,该版本的trx_id是80,小于readView的min_trx_id的值100,所以这个版本符合要求,吧name为刘备返回给用户。


我们看下一个场景,吧事务100的提交

# Transaction 100
BEGIN;
UPDATE hero SET name = '关羽' WHERE number = 1
UPDATE hero SET name = '张飞' WHERE number = 1;
COMMIT;

然后把事务id为200中的hero表number为1的更新下:

# Transaction 200
BEGIN;
# 更新了一些别的表的记录
...
UPDATE hero SET name = '赵云' WHERE number = 1;
UPDATE hero SET name = '诸葛亮' WHERE number = 1;

此刻hero表number为1的链表如下:


1,诸葛亮,蜀,200,call_pointer

1,赵云,蜀,200,call_pointer

1,张飞,蜀,200,call_pointer

1,关羽,蜀,200,call_pointer

1,刘备,蜀,200,call_pointer

其中1是页面显示的记录,后面2,3,4,5是undo日志的记录。

继续使用刚repeatable read隔离级别读取数据:


# 使用REPEATABLE READ隔离级别的事务
BEGIN;
# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备
# SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值仍为'刘备'

因为当前事务隔离级别是repatable read ,而之前执行selet1的时候已经生成了readView,所以直接复用之前的readView,之前readView里的m_ids里有【100,200】,min_trx_id为100,max_trx_id为201,creator_trx_id为0。

然后从版本链中挑选可见记录,因为诸葛亮的trx_id是200,包含在m_ids,所以不可见,查看下一条。

赵云的trx_id也是200,所以继续看下一条。

张飞的trx_id是100,不符合可见,继续看下一条。

关羽的trx_id也是100,继续看下一条。

刘备的trx_id是80,小于min_trx_id,所以符合版本可见,返回刘备给用户。

也就是说,两次结果select是相同的数据,这就是可重复读。如果我们吧事务200也提交,继续在事务中读一次,结果还是刘备。


MVCC小结


所谓的mvcc,就是multi version concurrent controller,多版本控制并发,在mysql中指在read committed、repeatable read在执行select的时候,可以并发操作,这样可以在不同事务读写和写读并发操作,从而提升性能。Read committed和repeatable read区别就是,read committed是每次普通select 都会生成一个readView,而compatable read则是第一次select生成,后面都是重复利用。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器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机制,为你在面试中遇到的相关问题提供全面的解答。
183 2
|
1天前
|
SQL 关系型数据库 MySQL
MySQL进阶突击系列(04)事务隔离级别、AICD、CAP、BASE原则一直搞不懂? | 看这篇就够了
本文详细介绍了数据库事务的四大特性(AICD原则),包括原子性、隔离性、一致性和持久性,并深入探讨了事务并发问题与隔离级别。同时,文章还讲解了分布式系统中的CAP理论及其不可能三角关系,以及BASE原则在分布式系统设计中的应用。通过具体案例和图解,帮助读者理解事务处理的核心概念和最佳实践,为应对相关技术面试提供了全面的知识准备。
|
2月前
|
存储 SQL 关系型数据库
MySQL的事务隔离级别
【10月更文挑战第17天】MySQL的事务隔离级别
124 43
|
24天前
|
关系型数据库 MySQL
mysql事务特性
原子性:一个事务内的操作统一成功或失败 一致性:事务前后的数据总量不变 隔离性:事务与事务之间相互不影响 持久性:事务一旦提交发生的改变不可逆
|
22天前
|
关系型数据库 MySQL 数据库
MySQL事务隔离级别及默认隔离级别的设置
在数据库系统中,事务隔离级别是一个关键的概念,它决定了事务在并发执行时如何相互隔离。MySQL提供了四种事务隔离级别,每种级别都解决了不同的并发问题。本文将详细介绍这些隔离级别以及MySQL的默认隔离级别。
|
1月前
|
存储 关系型数据库 MySQL
MySQL MVCC深度解析:掌握并发控制的艺术
【10月更文挑战第23天】 在数据库领域,MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种重要的并发控制机制,它允许多个事务并发执行而不产生冲突。MySQL作为广泛使用的数据库系统,其InnoDB存储引擎就采用了MVCC来处理事务。本文将深入探讨MySQL中的MVCC机制,帮助你在面试中自信应对相关问题。
150 3
|
2月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1695 14
|
2月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
2月前
|
存储 关系型数据库 MySQL
RR隔离级别在MySQL中的实现与幻读问题探讨
【10月更文挑战第3天】在数据库管理系统中,事务隔离级别是确保数据一致性和并发性能的关键要素。MySQL作为广泛使用的关系型数据库管理系统,支持多种事务隔离级别,其中可重复读(Repeatable Read,简称RR)是其默认隔离级别。本文将深入探讨RR隔离级别在MySQL中的实现原理,以及RR隔离级别下幻读问题的产生与解决方案。
119 2
|
2月前
|
SQL 关系型数据库 MySQL
【MySQL】索引和事务
【MySQL】索引和事务
61 0
下一篇
DataWorks