数据库的事务隔离与锁机制有什么差别和联系?

1. RC隔离级别下:

 SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;

在这里插入图片描述

1.1 RC下会出现不可重复读的问题

操作流程:

事务1事务2
begin;begin;
select * from mmall_user where id = 1;
UPDATE mmall_user SET email = ‘rc2’ WHERE id = 1;
COMMIT;
select * from mmall_user where id = 1;
COMMIT;

第一次读:第一次读
第二次读:第二次读
这就是不可重复读,即事务1中读取到了事务2已经提交的数据,导致了前后两次读取内容不同。

1.2 锁升级造成死锁、何时释放共享锁

事务1事务2
begin;begin;
select * from mmall_user where id = 1 lock in share mode;
UPDATE mmall_user SET email = ‘rc2’ WHERE id = 1;
UPDATE mmall_user SET email = ‘rc3’ WHERE id = 1;死锁,请问为什么会发生死锁呢?
COMMIT;
COMMIT;
  1. 解释一下为什么锁升级容易导致死锁:因为事务1获得了共享锁,共享锁与排它锁是互斥的,因此事务1在请求共享锁的时候会被当场阻塞,但是事务1不提交事务又不会释放共享锁,就造成了“不释放共享锁就无法获得排它锁,不获得排它锁就无法释放共享锁”的死锁局面。 有误
  2. 在所有拥有共享锁的事务提交后才会释放共享锁。

1.3 从锁的原理上说,为什么RC可以防止脏读?

操作流程:

事务1事务2
begin;begin;
select * from mmall_user where id = 1;
UPDATE mmall_user SET email = ‘排它锁’ WHERE id = 1;对id=1的那一行记录上写锁,其他事务无法获得这一行的锁,注意!不是无法访问这一行,而是无法获得这一行的锁,包括写锁(排它锁)和共享锁,我将在1.4中用实验证明这一观点
select * from mmall_user where id = 1; select操作是不加锁的,因此可以访问那条数据,因为事务2还未提交,因此访问不到最新的数据
COMMIT;
COMMIT;

1.4 用实验说话。

  1. 证明无法获得写锁
事务1事务2
begin;begin;
select * from mmall_user where id = 1;
UPDATE mmall_user SET email = ‘rc2’ WHERE id = 1;为id=1的那行记录加写锁
select * from mmall_user where id = 1 for update;在这里阻塞了,说明其他事务无法获得写锁
COMMIT;
COMMIT;
  1. 证明无法获得共享锁
事务1事务2
begin;begin;
select * from mmall_user where id = 1;
UPDATE mmall_user SET email = ‘rc2’ WHERE id = 1;为id=1的那行记录加写锁
select * from mmall_user where id = 1 lock in share mode;在这里阻塞了,说明其他事务无法获得共享锁
COMMIT;
COMMIT;

2. RR隔离级别下:

2.1 重试实验1.1,解决了不可重复读的问题,脏读自然也解决了。

2.2 在MySQL下, RR级别可否解决幻读问题?

  1. 首先看幻读是什么样子的,在RC级别下会产生RC:
事务1事务2
begin;begin;
select * from t_user where id > 4030;
update t_user set username = ‘4031c’ where id = 4031;
insert into t_user (username,pwd) values (‘zuihouyici’,‘xiangxinwo’);
COMMIT;
select * from t_user where id > 4030;
COMMIT;

第一次查询结果:在这里插入图片描述
第二次查询结果:在这里插入图片描述
这里在4031出现了不可重复读的问题,在4048出现了幻读的问题。
解释一下幻读和不可重复读的表现区别:不可重复读是原来有的记录发生变化,幻读是原来没有的数据出现了或者原来有的数据消失了。根本体现在,不可重复读是update带来的,幻读是delete和insert带来的。
2. MySQL中的RR是否能解决幻读,在RR级别下:

事务1事务2
begin;begin;
select * from t_user where id > 4030;
update t_user set username = '可重复读存在么?'where id = 4031;
insert into t_user (username,pwd) values (‘RR隔离级别’,‘幻读存在么?’);
COMMIT;
select * from t_user where id > 4030;
COMMIT;

第一次查询结果:在这里插入图片描述
第二次查询结果:在这里插入图片描述
提交后数据库结果:在这里插入图片描述
发现MySQL在RR模式下可以解决幻读问题。

2.3 凭什么RR能解决幻读问题?

  1. 一致非锁定读,也可以称为快照读,其实就是普通的读取即普通SELECT语句。
  2. 既然是快照读,故 SELECT 的时候,会生成一个快照。
  3. 生成快照的时机:事务中第一次调用SELECT语句的时候才会生成快照,在此之前事务中执行的update、insert、delete操作都不会生成快照。
  4. 不同事务隔离级别下,快照读的区别: READ COMMITTED 隔离级别下,每次读取都会重新生成一个快照,所以每次快照都是最新的,也因此事务中每次SELECT也可以看到其它已commit事务所作的更改;REPEATED READ 隔离级别下,快照会在事务中第一次SELECT语句执行时生成,只有在本事务中对数据进行更改才会更新快照,因此,只有第一次SELECT之前其它已提交事务所作的更改你可以看到,但是如果已执行了SELECT,那么其它事务commit数据,你SELECT是看不到的。
    所以上面的例子和锁无关,只是说明了RC的快照读每次读的都是表的最新快照,而RR读的快照是第一次select的快照。

2.4 那什么幻读问题会和锁有关呢?

在RR等级下:

事务1事务2
begin;begin;
select * from t_user where id > 4030 for update;
insert into t_user (username,pwd) values (‘RR隔离级别’,‘幻读存在么?’);阻塞
COMMIT;
select * from t_user where id > 4030 lock in share mode;
COMMIT;

在RC等级下:

事务1事务2
begin;begin;
select * from t_user where id > 4030 for update;
insert into t_user (username,pwd) values (‘RR隔离级别’,‘幻读存在么?’);不阻塞
select * from t_user where id > 4030 lock in share mode;阻塞
COMMIT;
此时解除阻塞,COMMIT;
  1. 为什么事务2不阻塞,因为在RC等级下没有间歇锁,因此RC等级下不会锁住索引在4030以上的所有,而RR会,因此RR阻塞,RC不阻塞。
  2. 为什么事务1的共享锁阻塞,因为事务2的insert需要当前读,因此加了写锁,而写锁和共享锁是互斥的,因此事务1的写锁无法降级成读锁,直到事务2提交后,事务1降级成读锁解除阻塞。
  3. 如下表,如果直接提交事务2,则事务1也不会阻塞。
事务1事务2
begin;begin;
select * from t_user where id > 4030 for update;
insert into t_user (username,pwd) values (‘RR隔离级别’,‘幻读存在么?’);不阻塞
COMMIT;
select * from t_user where id > 4030 lock in share mode;不阻塞
COMMIT;

3. 实验小结,非常重要

  1. MySQL的RC和RR的select操作(无for update/lock in share mode时),默认不加锁,采取快照读的方式,区别是RC快照读时每次都生成快照,因此会出现不可重复读,但是不会发生脏读,为什么?因为在事务提交之前均不会改变表的快照,因此在事务2提交之前,事务1无论如何select都是同一个数据。RR快照读只在第一次select时生成快照,因此不会出现不可重复读。

  2. 在RC下,事务1的select where id > 1 for update 这样的范围加锁查询不会阻塞事务2的insert,因此没有加间隙锁。RR级别下会阻塞事务2的insert,因为加了间隙锁,这也说明了间隙锁可以解决幻读。

  3. 小结:在MySQL中,做到了读一定是并发无竞争的,因为读的都是快照,只是RC/RR的快照生成规则不同罢了。但是写都是当前读,即是存在锁竞争的,RC下不加间隙锁,只对记录的索引加锁,RR下不仅对记录的索引加锁,还加间隙锁,因此解决幻读问题。

4 既然RR已经解决了幻读,那mysql还要串行化干什么?

借用一个的例子:

例子:1 A事务开启,B事务开启。
2 B事务往表里面插入了一条数据,但还并未提交。
3 A事务开始查询了,并没有发现B事务这次插入的数据。然后此时B事务提交了数据。
4 于是乎,A事务就以为没有这条数据,就开始添加这条数据,但是却发现,发生了数据重复添加。

在这个情况下,恰恰是因为RR的快照读导致A事务根本无法感知到事务B对表产生的影响,而串行化相当于对全表加锁,一切事务都是串行的,因此事务A必然能感知事务B的添加,因为事务A会在事务B之后才begin。

5.没有MVCC的数据库怎么通过锁实现隔离等级?

还有一个问题挥之不去,对其他没有采用MVCC的数据库而言,锁和数据库的事务隔离有什么关系呢?我没有做实验,但是通过我的资料阅读和理解,给出以下方案,不一定正确但是具有一定指导意义:

约定1. 没有MVCC,所有读都是当前读。

约定2.读锁的意思是:所有事务都可以select,但是不可以写。写锁的意思是:本事务可以读写,其他事务不可以读写。注意,此处的写锁定义和MySQL的写锁表现形式不同,MySQL写锁的表现形式为:事务1上写锁后,事务2无法上锁,但是可以快照读,即可以select。此处写锁的表现是形式:事务1上写锁后,事务2读写均立即阻塞,因为不存在快照读。

预定3.RC级别下,写语句及select for update加写锁,单纯的select语句不加锁,但是会因为其他事务的写锁而阻塞

约定4.RR级别下,select语句(哪怕没有lock in share mode)也是读锁(即共享锁),写语句不加锁,但是会因为其他事务的共享锁而被阻塞
在这里插入图片描述

场景1结论:事务2提交后,事务1获得读资格,因此会出现不可重复读,但不出现脏读。

在这里插入图片描述
场景2结论:事务2的所有写操作都阻塞,因此事务2不可能提交写操作,所以事务1也根本不可能读到事务2的写操作,因此解决了不可重复读。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值