MySQL中的数据库隔离级别

插播面试题

  1. MySQL中事务隔离级别有哪几种?
  2. 事务隔离级别的底层实现原理是什么?

问题一可能大家都有所了解,事务隔离级别嘛,不就是读未提交(read uncommitted)读已提交(read committed)可重复读(repeatable read)串行化(serializable)这几种嘛

这时候面试官可能又会问:这几种隔离级别分别解决了什么问题,又有什么缺点?

到这里没深入看过隔离级别原理或者自己没写过Demo的小伙伴可能就蒙蔽了,可能脑海中隐约有不可重复读幻读这几个概念,但是一片混乱

针对问题二,可能就更蒙蔽了,我怎么知道隔离级别底层是怎么实现?难倒是加锁?

要解答这几个问题,还是得先从有哪些事务隔离级别说起

事务隔离级别

MySQL为了保证数据操作的一致性,因此有事务这么一个概念,同一个事务中所有的增删改操作,要么一起成功,要么一起失败。

事务隔离性是针对多个事务同时访问同一份数据而提出的概念,理论上在某个事务对数据进行访问、修改时,若其他事务也想访问这份数据,则应该进行排队,当之前一个事务提交之后,其他事务才可以继续访问这个数据。也就是传统意义上的串行化排队等待。但是我们知道串行是一种性能及其低下的方案,如果其他事务只想读取数据呢?串行显然不是一种最优方案,MySQL为了提升并发度,权衡性能和数据安全性,提出了各种隔离级别

下面以实例说明,假设有如下User表,表中初始化内容如下,2条用户数据

idname
1name1
2name2

读未提交(read uncommitted)

顾名思义:B事务能够读取到A事务未提交的数据,即为读未提交

如图所示,假设A、B两个事务执行顺序如下,A事务执行过程中修改了name1的值为name3,但是还没提交,事务B就已经能读取到name3的值了

时间发生顺序事务A事务B
1start transactionstart transaction
2select name from user where id = 1(读取到的是name1)
3update user set name1=‘name3’ where id = 1(还没提交)
4select name from user where id = 1(读取到的是name3)

读未提交是不符合大部分业务场景需求的,我都没提交的数据,被你读取到了,万一A事务此时做了回滚操作,但是B事务已经取到了修改后的值,那就违背了一致性的原则。

同时事务读取到了一个无效的值(可能被回滚了)的情况即称作脏读

平时使用中,需避免使用读未提交隔离级别

读已提交(read committed)

如果B事务每次都只能读取到A事务提交(Commit)后的数据,那么脏读问题也就解决了,同时这种隔离级别称为读已提交

同样的还是以表格来说明:事务B在事务A执行commit操作之前是无法读取到name3的值的

时间发生顺序事务A事务B
1start transactionstart transaction
2select name from user where id = 1(读取到的是name1)
3update user set name1=‘name3’ where id = 1
4select name from user where id = 1(读取到的还是name1)
5commit
6select name from user where id = 1(读取到的是name3)

这种隔离级别又有什么问题呢?我们发现只要其他事务修改并提交了数据,则B事务中始终能查询到修改后的值,也就是查询同一条数据时,同一事务的前后两个时间点中查询到的数据值不一致(其他事务进行了修改、提交)

这种在同一个事务中的不同时间点查询同一个数据,但是数据不一致的情况称为不可重复读

可重复读(repeatable read)

一个事务只能读到另一个已经提交的事务修改过的数据,但是第一次读过某条记录后,即使其他事务修改了该记录的值并且提交,该事务之后再读该条记录时,读到的仍是第一次读到的值,而不是每次都读到不同的数据。那么这种隔离级别就称之为可重复读

如图所示,在时间节点6时,A事务已经提交,但是B事务还是读取到的是name1,只有在B事务提交后再查询,才能查询到最新的值

时间发生顺序事务A事务B
1start transactionstart transaction
2select name from user where id = 1(读取到的是name1)
3update user set name1=‘name3’ where id = 1
4select name from user where id = 1(读取到的还是name1)
5commit
6select name from user where id = 1(读取到还是name1)
7commit
8select name from user where id = 1(读取到是name3)

同一事务中始终读到的是事务开始时数据的状态,即为可重复读

幻读的错误理解

可重复读隔离级别下又有什么问题呢?很多人网上搜可重复读隔离级别下有什么问题时,可能都会得到答案:可重复读下存在幻读的问题

并且给出了幻读的例子如下:假设User表中目前有2条数据,A、B同时开启事物,A向User中插入一条数据,则B事务中在2个时间节点查询到User表中数据不一致(后面查询多了一条数据),则称为幻读,用表格来说明的话即为如下步骤

时间发生顺序事务A事务B
1start transactionstart transaction
2select * from user(读取到1、2两个用户,没用户3)
3select * from user (读取到的也是用户1、2两个用户)
4前面没查到用户3,执行插入操作
INSERT INTO .user(id, name) VALUES (3, ‘name3’)
5commit
6select * from user (读取到的是用户1、2、3三个用户,多了一个用户)

总的来说就是一句话:即可重复读操作不能阻塞或避免其他事务的插入insert、删除delete,其他事务插入或删除数据后,当前事务查询结果与前一次查询结果不一致

仔细想一想网上这种说法对不对?我个人认为前半句对,后半句不对

  1. 虽然MVCC机制(快照读取数据历史版本)能保证前后读取到一致的数据(可重复读),但不能阻塞其他事务的插入、删除操作

  2. MySQL中InnoDb引擎下由于存在MVCC机制(下篇文章再做介绍),在RR隔离级别下,不管你怎么读,同一事务中都是读取到一致的数据的,因此不存在前后读取到数据不一致的情况,如果前后数据读取到的数据不一致,这是否也可以算不可重复读的一种呢,而并不是真正的幻读

幻读的正确理解

考虑如下场景,A、B两个事务同时要进行插入User3(id=3,name=3)的操作,正常逻辑就是判断这个用户是否存在,不存在则进行插入,执行顺序如下

时间发生顺序事务A事务B
1start transactionstart transaction
2select * from user(读取到1、2两个用户,没用户3)
3select * from user (读取到的也是用户1、2两个用户)
4前面没查到用户3,执行插入操作
INSERT INTO .user(id, name) VALUES (3, ‘name3’)
5commit
6前面没查到用户3,执行插入操作
INSERT INTO .user(id, name) VALUES (3, ‘name3’)
此时会抛异常,主键重复Duplicate entry ‘3’ for key ‘PRIMARY’

即B事务中,根据之前时间节点3的查询条件来判断用户3不存在,因此产生幻觉“认为”数据库中不存在这个用户,此时发起插入操作,发现报错(实际库中已经存在User3)

幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读,即前一次查询给了用户一种仿佛用户3不存在的幻觉

行锁、间隙锁

MySQL中为了避免幻读,引入了锁的概念(行锁、间隙锁、Next-key Lock),什么意思呢?

如果B事务中第一次查询时就加上间隙锁(防止其他事务进行插入),则A事务就不能进行插入操作,则B事务后续的插入用户操作不会报错(没有出现幻读)

时间发生顺序事务A事务B
1start transactionstart transaction
2select * from user(读取到1、2两个用户,没用户3)
3select * from user where id = 3 for update (读取到的也是用户1、2两个用户)
4
INSERT INTO .user(id, name) VALUES (3, ‘name3’)
阻塞等到B事务提交
5前面没查到用户3,执行插入操作
INSERT INTO .user(id, name) VALUES (3, ‘name3’)
插入成功
6commit
71062 - Duplicate entry ‘3’ for key ‘PRIMARY’

如图时间点3中,B事务查询操作中加入for update操作,由于id=3的记录不存在,因此加的是间隙锁,此时A事务执行INSERT语句时会阻塞,直到B事务提交,因此B事务中后续插入成功、没有出现幻读。关于锁的更多内容、本文不再介绍。

看到这里,大家可能就明白了为什么RR级别是MySQL下默认的隔离级别,它是一种性能和安全性考虑的折中方案,虽然存在幻读的情况,但是可以通过人为加锁控制解决幻读的情况

串行化(serializable)

串行化即为传统意义上的排队等待,所有事务串行执行,需要等待上一个事务结束,当前事务才能得以执行,即使当前事务中进行的读操作。

这种隔离级别拥有最高的安全性,但性能上也损失严重。

看到这里文章开头第一个问题MySQL中事务隔离级别有哪几种? 相比各位已经有了答案了。还剩第二个问题

事务隔离级别的底层实现原理是什么?

这里说的事务隔离级别的底层实现原理,主要针对的就是读已提交和可重复读隔离级别,其实关键点就是2个

  1. 读已提交中隔离级别下,MySQL是怎么保证每次查询都读取到最新提交的数据的,却查询不到未提交的数据
  2. 可重复读隔离级别下,MySQL又是怎么使得事务中后续的每次查询都和第一次保持一致的?

下篇文章中会对读已提交、可重复读中的快照读原理(MVCC)做详细介绍

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值