深挖MySQL —— 事务(五)事务的隔离级别

        在聊事务的隔离级别前,我们先看看事务之间若是没有隔离性,会产生什么问题。总的来说,我们将它总结为三种情况:脏读(Dirty reads)、不可重复读(Non-repeatable reads)、幻读(Phantom reads)。下面来看看它们发生的场景以及导致的后果:

        脏读(Dirty reads):事务A读取到了事务B还没有提交的数据,并在此基础上进行操作。如果B事务rollback,那么A事务所读取到的数据就是不正确的,会带来问题。

        不可重复读(Non-repeatable reads):在同一事务范围内读取两次相同的数据,所返回的结果不同。比如事务B第一次读数据后,事务A更新数据并commit,那么事务B第二次读取的数据就与第一次是不一样的。

        幻读(Phantom reads):事务A读取到了另一个事务B新提交的数据,不同于不可重复读的是,幻读发生在事务B提交Insert操作的场景。比如说,事务A对一个表中所有行的数据按照某规则进行修改(整表操作),同时,事务B向表中插入了一行原始数据,那么后面事务A再对表进行操作时,会发现表中居然还有一行数据没有被修改,就像发生了幻觉一样。

        为了解决以上问题,事务的隔离性I不可或缺,由于时空成本的问题,事务又引入了隔离级别的概念,相对的隔离级别程度越高越安全,但是效率越低。事务的隔离级别划分为:

        Read uncommitted读未提交:最低级别,以上情况均无法保证,仅仅保证事务的原子性和持久性。

        Read committed读已提交:可避免脏读情况发生,oracle默认事务隔离级别。

        Repeatable read可重复度:字面意思,可以避免脏读和不可重复度,在Innodb的实现中,由于区间锁的缘故,也可以避免幻读,Innodb将它设置为默认隔离级别。

        Serializable串行化可避免脏读、不可重复读、幻读情况的发生。相当于Java的重量级锁,效率极低。

        事务的四个隔离级别只是标准SQL规范中的定义,具体的实现由各个数据库自行提供。因此实现的程度也是各不相同。像Innodb的repeatable read级别以及基本上实现了serializable的效果。下面我们来看看Innodb是付出了什么代价,如何实现这四个隔离级别的。

        Read uncommitted

        隔离级别“读未提交”作为最低级别,没有避免任何会发生的坏情况,因此Innodb没有付出任何代价,仅仅是由redo log和undo log来保证事务的原子性A、持久性D。当只有一个事务的情况的时候也能保证事务的一致性C:当有两个事务以上,事务A操作用户1给用户2转账,此时用户2的数据正在被另一个事务B操作,此时无法保证事务的一致性:

事务A事务B
begin;begin;
用户1余额扣款100元用户2余额信息某修改操作
用户2余额增加100元......
commit;由于某种操作导致事务回滚
rollback;

        可以看到此时用户1的余额已被扣除,而用户2的余额将处于一个错误的状态。事务的一致性遭到破坏,因此隔离级别读未提交只推荐用于实时性要求不高的只读操作。

        Read committed

        隔离级别读已提交会将update操作的写锁保留到事务提交后才释放,避免了脏读的出现。同时由于上篇提到的Innodb在读已提交隔离级别下虽然使用了快照读(快照读原理请看上篇),但是Read View在每次快照读的时候才生成,因此读已提交级别是无法避免不可重复读问题的。

        Repeatable read

        隔离级别可重复读相对读已提交级别,添加了区间锁的代价(区间锁原理请看上篇),并且由于使用了快照读,并且Read View生成在事务第一次执行快照读的时间点,因此可以避免不可重复读和幻读的问题。但是需要注意的是MVCC是有失效的场景的,举例如下:

1、新建表demo01:

CREATE TABLE `demo01` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`age` INT(11) NOT NULL,
PRIMARY KEY (`id`),
INDEX `name` (`name`)
)

2、插入测试数据 

 3、开始事务A和事务B操作数据

事务A事务B
begin;begin;

select * from demo01 where id=3;

结果集:{id:3,name:张三3,age:3}

select * from demo01 where id=3;

结果集:{id:3,name:张三3,age:3}

update demo01 set name='zhangsan3' where id=3;

commit;

select * from demo01 where id=3; // 快照读

结果集:{id:3,name:张三3,age:3}

update demo01 set age=30 where id=3;

select * from demo01 where id=3; // 当前读

结果集:{id:3,name:zhangsan3,age:30}

         已经搭建了环境的小伙伴可以操作试试,我们可以发现,当事务A对数据执行了update操作后,下一次对数据select就不是使用快照读了,而是会把当前数据读出,这使的靠MVCC实现的避免不可重复读和避免幻读的效果没有了。当然,由于区间锁的存在,Repeatable Read级别仍旧可以保证对幻读的避免(原理请看上一篇)。

        Serializable

        串行化级别不必多说,绝对可以避免以上一切问题,原因是不管使读写数据,都会加锁,并将锁保留到事务结束以后。但是可想而知,并发的情况下串行化的隔离级别效率低的可怕,不推荐使用。

如有错误,敬请斧正;欢迎转载,但请务必注明出处;最后,在此向神奇的海螺保证,绝不太监!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值