[toc]
前言
这篇文章将会总结MySQL中的innodb的事务隔离级别,以及幻读、脏读、不可重复读。
事务隔离级别概述
MySQL中,innodb所提供的事务符合ACID的要求,而事务通过事务日志redo log和undo log满足了原子性、一致性、持久性,事务还会通过锁机制满足隔离性,在innodb存储引擎中,有不同的隔离级别,他们有着不同的隔离性。
什么是事务的隔离级别?如果只是从概念上理解的话可能比较模糊,咱们直接看看不同的隔离级别下的实际表现是什么样子的,在结合理论理解,就会明了很多,首先打开两个终端,同时连接到当前的数据库,如下图所示,我们对这两个会话进行编号,并且以颜色区分,1号会话使用黄色标识,2号会话使用红色进行标识,使用show processlist语句,可以看到已经有两个线程链接到当前数据库中了。
两个会话使用相同的数据库,并且同时开启一个事务。
由于下面的所有操作会在两个会话中来回切换,所以方便描述,我们每个操作顺序进行编号,例如下图,我们现在会话1的事务1中执行了更新操作,然后在事务1中执行了查询操作,最后又在会话2中的事务2中执行了查询操作,按照操作顺序,为各个操作进行了顺序编号。
从上图可以看到,在事务1中显示的数据已经发生了改变,第2条数据对应的字符串已经变为test,事务2中显示的数据未发生变化,没错,你肯定会说,那是因为事务1还没有提交,所以事务2中还无法看到别修改的数据,那么我们将会话1中的事务提交试试看,看看事务1提交后,事务2中的数据会不会发生改变。在事务提交之前,我们现在两个会话中再次查询一次,两个事务中的数据显示如下图中的操作1与操作2的显示结果:
然后我们执行上图中的第3步,将事务1中的修改操作进行提交,在事务2中再次查看t1表中的数据(第4步),进过查看发现,t1表中的第2条数据对应的字符串仍然没有发生改变,这种情况可能和我们想象的状况有些不同,在不了解事务的隔离级别之前,可能会认为,当上图的事务1提交以后,事务2中再次查询同一张表的数据时,应该会看到事务1中修改的数据,但实际上并没有,在会话2没有提交之前,从t1表中查询出的数据一致都是不变的,直到会话2提交以后,在此查询t1表的数据,才发现t1表的第二条数据对应的字符串已经发生了改变,出现这种现象,是因为mysql的默认隔离级别造成的,而不同的隔离级别会提现不同的隔离效果,所以事务的隔离级别,决定了各个事务之间的隔离性,看到值了,应该对事务的隔离性有了一个大概的了解,但是具体有几个隔离级别,每个隔离级别下有哪些特性,慢慢总结。
此处,我们先列出innodb中的所有隔离级别,然后逐个了解他们,事务个隔离级别一共分为如下四种:
- READ-UNCOMOITTED:此隔离级别翻译为读未提交
- READ-COMMITTED:此隔离级别翻译为读已提交或读提交
- REPEATABLE-READ:此隔离级别翻译为可重复度
- SERIALABLE:串行化
mysql默认的设置的隔离级别为REPEATABLE-READ即可重复度,使用如下语句可以查看当前设置的隔离级别:
show variables like 'tx_isolation'
,如下图所示,默认设置的隔离级别为可重复读:
如果修改my.cnf配置文件,则可以通过如下参数配置mysql的事务隔离级别,注意不是使用tx_isolation,而是使用:transaction_isolation=REPEATABLE-READ
隔离级别:可重复读
先来总结一下可重复度隔离级别的特性,仍然刚才文章开头示例为例,下图中,会话1和会话2中同时开启两个事务,在事务1的事务中修改t1表数据以后(将第二条数据t1str的值改为test),事务2中查看到数据仍然是事务1修改之前的数据,即使事务1提交了,在事务2没有提交之前,事务2中查看到的数据都是相同的,比如t1表中的第2条数据,不管事务1是否提交,在事务2没有提交之前,这条数据对于事务2来说一直是没有发生改变的,这条数据在事务2中是可以重复的被读到,所以这种隔离级别被称为可重复读。
但是,你可能会有个问题,之前说过,事务的隔离性是由锁来实现的,那么上图的事务1中执行更新语句,事务1中应该对数据增加了写锁,但是在事务2中,仍然开源进行查询操作,即进行读操作,可是写锁是排它锁,在事务1中已经添加了写锁的情况下,事务2为什么可以读取?这是因为innodb采用了一致性非锁定读的机制提高了数据库并发性,一致性非锁定读表示在如果当前行被施加了排它锁,那么当需要读取行数据时,则不会等待行上的锁的释放,而是读取一个快照数据,如下图所示:
上面展示了innodb中一致性非锁定读的过程,之所以称其为非锁定读,是因为他不需要等待被访问的行上的排它锁的释放,而上图中的快照的实现是由事务日志所对应的undo段来完成,其实快照就是该行所对应的之前的版本数据,即历史数据,一行记录可能有不止一个快照,并不是所有的隔离级别都是用了一致性非锁定读,在可重读和读提交的隔离级别下,innodb存储引擎使用了一致性非锁定读,但是在这两个隔离级别中,对于快照数据的定义不同,在可重复读隔离级别下,快照数据是指当前事务开始数据的样子,所以在刚才示例中,事务2中t1表对应的第二条数据记录的t1str的值一直都是2,因为在事务2开始的时候,其值就是2,这也是其可重复读的特性,但是在读提交的隔离级别下,由于对于快照的定义不同,所以显示的现象也不同,这在做读提交隔离级别的实验时自然会明白。
幻读
在可重复读的隔离级别下,可能会出现幻读的问题,那么什么是幻读,一起来看一下:
从上图可以看出,从第5步开始,数据其实就已经发生了改变,到第7步时,事务2还是无法看到数据改变,但是当事务2更新数据以后,发现莫名其妙的多出了一条数据,在同一个事务中,执行两次同样的SQL,第二次sql会返回之前不存在的行,或者之前的出现的数据不见了,这种现象被称为幻读。
注:上例中第8步执行的update语句中并没有指定任何条件,相当于更新表中的所有字段,如果指定了条件,并且没有更新到隐藏的行,那么可能无法看到幻读的现象
事务隔离级别:串行化
上面的例子我们可以发现,事务处于REPEATABLE-READ(可重复读)级别时,会出现幻读的情况,而在之前我们已经提到过,不同的隔离级别所引入的问题会有所不同的,隔离性也有所不同,那么有没有一种隔离级别,能够解决幻读的问题呢?那就是SERIALIZABLE(串行化)隔离级别就不会出现幻读的问题,我们来试试将事务的隔离级别设置为串行化,事务是怎样的工作的。
首先,我们将两个会话中的事务的隔离级别都设置为SERIALIZABLE:串行化
如上图所示,当将两个会话中的事务隔离级别同时设置为串行化以后,分别在两个会话中开启事务1与事务2,如上图中的第1步和第2步所示,然后进行第3步,事务1中插入了一条数据,此时执行第4步,在事务2中查询表t1的数据,可以看到第4步好像被卡住了,等一会儿,发现第4步并没有执行成功,而是报了一个错误,如下图:
从报错信息可以发现,事务2中的锁请求超时了,我们之前提到,事务的隔离性是由锁实现的,当我们使用串行化的隔离级别时,由于事务1先对t1表施加了写锁,所以当事务2对t1表请求读锁超时,会被阻塞,那么出现请求锁超时的情况,也就算是比较正常了,所以此时我们无法在事务2中读到t1表的数据。
那么,换一种实验,我们在事务1中从插入一条数据,然后在事务2中执行查询t1表的语句,此时事务2中查询语句会阻塞,这是我们提交事务1,看看会发生什么情况。
趁着事务2中查询语句被阻塞的时候,将事务1进行提交,如下图所示:
当在事务2中执行查询语句时,查询被阻塞,此时事务1被提交,当事务1被提交的一瞬间,事务2的语句已经查询出结果,从结果可以看出,这个查询语句被阻塞了31秒左右的时间,当事务1中的写锁释放时,事务2才读出了数据,从上述实验来看,当事务处于串行化隔离级别时,是不可能出现幻读的情况的,因为如果另一个事务中对表添加了写锁,那么当前事务中是无法读到数据的,必须等到另一个事务提交,另一个事务释放了对表的写锁,当前事务才能进行申请读锁,使用串行化的隔离级别不会出现幻读的额情况,但是,当事务的隔离级别设置问串行化,数据库失去了并发能力,所以很少将隔离级别设置为串行化,因为这种隔离性过于严格。
隔离级别:读已提交
现在了解了两种隔离级别,可重复度和串行化,也了解到,串行化隔离级别的隔离性最强的,没有并发能力,可重复读级别稍微次之,并发能力较好,但是存在幻读现象。
那么现在聊聊读提交。
同样,在两个会话同时开启两个事务,在事务1中修改t1表的第二条数据,如下图1、2步所示,此时事务1并未提交,所以如第3步所示,在事务2中并无法看到事务1中的修改,而懂事务1提交以后,事务2中即可看到事务1中的修改,换句话说,就是事务2能够读取到事务1提交后的更改,这种隔离级别称为读提交。
在读提交的隔离级别写,也会出现幻读的问题,如下:
在上述示例中,事务1向t1表中插入一行数据,在事务1提交以后,事务2中即可看到,但是事务2还没有提交,在事务2中执行两次相同的查询语句,莫名其妙的多出了一行,出现了幻读的情况。
在读已提交的隔离级别下,除了会出现幻读的情况,还会出现不可重复读,不可重读表示不一定可重复,仍然以刚才的第一个示例为例,下图中第3步中,获取到第2条记录的t1str字段的值为test,而在同一个事务中,第5步所查出的第二条记录对应的t1str字段却变成了ttt,所以我们想要再次重复读到刚才的test。就变成了不可重复读。
其实不可重读与幻读的表象比较相似,都是在同一个事务中,并没有操作某些数据,可是这些数据莫名其妙的被改变了,或者突然多出了某些数据,又或者突然少了某些数据,这些状况好像都能用幻读这个词去理解,所以一开始总是分不清到底什么是幻读,而且mysql官方文档中也把不可重读归为幻读了,只是大家为了更加细化他们区别,把他们分成了不可重读与幻读,如果是在无法分清,我们可以理解,幻读:的重点在于莫名其妙的增加了或减少了某些数据。不可重复读:重点在于莫名的情况下,数据被修改更新了。
总结一下读提交这个隔离级别,会出现不可重读和幻读问题,比可重读隔离级别的问题更多,但是他的并发能力比可重读更强,隔离级别越低,并发能力就越强,存在的问题就越多。
隔离级别:读未提交
将两个事务隔离级别同时设置为读未提交,然后两个会话中各自开启一个事务,然后在事务1中插入一条数据,并且删除一条数据,如下图,第1步和第2步所示:
第1、2步执行完毕以后,事务1并未提交,此时执行步骤3,在事务2中查看t1表中的数据,是可以看到事务1中所作出的修改的。
所以我们可以发现,在读未提交的隔离级别写,即使别的事务所做修改并未提交,我们也能看到其修改的数据,当前事务能够看到别的事务未提交的数据,我们成为脏读,上面示例中,事务1并未提交,但是所做的修改事务2中可以查看到,由于事务1中的修改有可能回滚,或者数据有可能继续被修改,所以事务2中看到到数据飘忽不定,并不是最终的数据,并不是提交后的数据是脏的,但是事务2中仍然看到这些数据,所以这种称之为脏读,当事务的隔离级别处于读未提交时,并发能力是最强的,但是隔离级性与安全性是最差的。这个隔离级别时,会出现脏读、不可重复读、幻读的问题。
脏读、幻读、不可重复读的区别
- 脏读:当前事务可以查看到别的事务未提交的数据(侧重点在于别的事务未提交)
- 不可重读:表示同一事务中,查询相同的数据范围时,同一个数据资源被改变(侧重点在于更新修改数据)
- 幻读:表现与不可重读容易搞混,区别就是:幻读的侧重点在于新增和删除,表示在同一个事务中,使用相同的查询语句,第二次查询时,莫名的多出了一些之前不存在的数据,或者莫名的不见了一些数据
不同的隔离级别拥有的问题
首先隔离级别越高,隔离性越强,所拥有的额问题越少,并发能力越弱。