深挖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
串行化级别不必多说,绝对可以避免以上一切问题,原因是不管使读写数据,都会加锁,并将锁保留到事务结束以后。但是可想而知,并发的情况下串行化的隔离级别效率低的可怕,不推荐使用。
如有错误,敬请斧正;欢迎转载,但请务必注明出处;最后,在此向神奇的海螺保证,绝不太监!!!
标签:事务,隔离,深挖,幻读,MySQL,级别,id,name 来源: https://blog.csdn.net/fjgcxygmxxyx/article/details/120229981