mysql的不可重复读怎么实现的(MySQL中的脏读不可重复)
mysql的不可重复读怎么实现的(MySQL中的脏读不可重复)脏读 Dirty read事务隔离级别在什么场景下面会出现脏读?在什么场景下会出现不可重复读?在什么场景下又会出现幻读?出现的各个问题又是如何修复的?下面,我们将对不同的事务隔离级别,逐步地去验证脏读、不可重复读、幻读是怎么样发生的,又是如何通过调整事务的隔离级别逐步修复它们的。在开展我们的实验过程之前,我们首先要明确,事务的隔离级别和脏读、不可重复读、幻读之间存在如下的关系映射:
关注我「程序猿集锦」,获取更多分享。
文章目录结构
前言在不同的事务隔离级别下,会出现各种各样的问题。诸如我们平时经常听说的:脏读、不可重复读、幻读,在不同的事务隔离级别下,会在特定的场景下出现。
那么到底什么时候脏读?什么是不可重复读?什么又是幻读?
在什么场景下面会出现脏读?在什么场景下会出现不可重复读?在什么场景下又会出现幻读?出现的各个问题又是如何修复的?
下面,我们将对不同的事务隔离级别,逐步地去验证脏读、不可重复读、幻读是怎么样发生的,又是如何通过调整事务的隔离级别逐步修复它们的。
隔离级别与读之间的关系在开展我们的实验过程之前,我们首先要明确,事务的隔离级别和脏读、不可重复读、幻读之间存在如下的关系映射:
事务隔离级别 |
脏读 Dirty read |
不可重复读Non-Repeatable read |
幻读Phantom read |
读未提交 Read Uncommitted |
可以出现 |
可以出现 |
可以出现 |
读已提交 Read Committed |
不可能出现 |
可以出现 |
可以出现 |
可重复读 Repeatable Read |
不可能出现 |
不可能出现 |
可以出现(MySQL中可以避免出现) |
串行化 Serializable |
不可能出现 |
不可能出现 |
不可能出现 |
其中上面提到的事务隔离级别,都有各自的简称,我们要了解一下他们的简称都是什么,如下:
- 读未提交 Read Uncommitted,简写为:RU。
- 读已提交 Read Committed,简写为:RC。
- 可重复读 Repeatable Read,简写为:RR。
- 串行化 Serializable,没有简称。
同时我们需要了解,上述表格中的事务隔离级别,从上往下,依次增高。也就是说事务的隔离级别:RU < RC < RR < Serializable。随着事务隔离级别的增高,数据库的并发性也就随之降低。隔离得越严实,并发性就越低。
脏读、不可重复读、幻读的定义- 脏读:一个事务读取了其他事务update修改或delete删除或insert插入后但是还uncommitted未提交的数据内容。例如一个事务A在执行的过程中,它读取到了另外一个事务B修改后(修改操作包含:insert、delete、update三种操作)还未提交的数据内容。
- 这里说明一下,为什么把读取到其他事务delete删除或insert插入但是还uncommitted未提交的数据也称为脏读,从读取到其他事务uncommitted未提交操作这个动作来理解,uncommitted的内容,意味着是不确定的可能回滚的脏数据,所以定义为脏读更合适。
- 不可重复读:一个事务在它执行的过程中,在前后两个不同的时间点使用同样的条件查询进行查询,查询到的数据内容却不相同,这个数据内容不相同的原因是其他事务update修改或delete删除了当前事务查询条件范围内的数据,并且已经提交,这就是不可重复读。例如一个事务A在执行的过程中,它先根据自己的查询条件查询到一个结果集,然后另外一个事务B修改了一些数据,或者删除了一些数据,这些数据刚好又在事务A的查询条件范围内,并且事务B已经提交自己的修改或删除的操作;此时事务A再次使用同样的查询条件去查询,发现得到的结果集和第一次查询得到的结果集不相同,这种现象就对于事务A来讲就属于不可重复读。
- 幻读:一个事务在它执行的过程中,在前后两个不同的时间点使用同样的条件查询进行查询,查询到的数据内容却不相同,这个数据内容不相同的原因是其他事务insert插入了当前事务查询条件范围内的数据,并且已经提交,这就是幻读。幻读仅仅指insert插入操作,update和delete不在此范畴内。例如一个事务A在执行的过程中,它先根据自己的查询条件查询到一个结果集,然后另外一个事务B插入了一些数据,这些数据刚好又在事务A的查询条件范围内,并且事务B已经提交自己插入的操作;此时事务A再次使用同样的查询条件去查询,发现得到的结果集和第一次查询得到的结果集不相同,这种现象就对于事务A来讲就属于幻读。
- 注意:读取到其他事务insert插入但还uncommitted未提交的数据是属于脏读的范围,不属于幻读的范畴。幻读特指读取到其他事务insert插入且committed已提交的数据。
不要问为什么delete删除的操作不属于幻读的范畴,只有insert插入操作才属于幻读的范畴,因为这个是鳄鱼的臀部:规定。开玩笑了,具体的原因是因为在SQL92标准中有明确的定义,如下所示:
上图可以参考链接:http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
在SQL92标准中,就是上面截图中那么定义的,我们知道它是怎么定义就可以了。其实,当事务A第二次读取后,如果发现少了一些行,而这些行是因为事务B给删除并且已经提交导致的,刚才查询的时候还有呢,现在怎么没有了,也像是发生了幻觉一样。这样也能说得通,看怎么理解吧。
注意脏读、不可重复读、幻读三者之间的几点区别
- 脏读和不可重复读的区别是:不可重复读是读取到其他事务committed已提交的数据,而脏读是读取到其他事务uncommitted未提交的数据。
- 幻读和不可重复读的区别是:不可重复读是读取到其他事务update修改或delete删除且已经committed提交的数据,而幻读专指读取到其他事务insert插入且已经committed提交的数据。
- 脏读和幻读的区别:幻读是强调一个事务读取到其事务insert插入且committed已提交的数据,而脏读是读取到其他事务uncommitted未提交的数据,这个未提交的操作可以是update、insert、delete中的任何一种。
实验过程用到的测试数据如下:
drop table if exists t;
create table t(id int primary key v int);
insert into t values(1 1);
insert into t values(2 2);
select * from t;
涉及到的命令语句如下:
/*设置当前session的事务隔离级别为RU,下面两种方式都可以。*/
set session transaction isolation level read uncommitted;
set session transaction_isolation = 0;
/*设置当前session的事务隔离级别为RC,下面两种方式都可以。*/
set session transaction isolation level read committed;
set session transaction_isolation = 1;
/*设置当前session的事务隔离级别为RR,下面两种方式都可以。*/
set session transaction isolation level repeatable read;
set session transaction_isolation = 2;
/*设置当前session的事务隔离级别为Serializable,下面两种方式都可以。*/
set session transaction isolation level serializable;
set session transaction_isolation = 3;
/*开始事务,以下两种方式都可以。*/
start transaction with consistent snapshot;
begin;
RU读未提交
在读未提交事务隔离级别下,所有的现象都可以发生。实验过程详见下面的各个步骤。
脏读RU读未提交-update操作引起的脏读下面实验过程是事务A读取了事务B修改后uncommitted未提交的数据,导致事务A出现了脏读的情况。
RU读未提交-delete操作引起的脏读下面实验过程是事务A读取了事务B删除后uncommitted未提交的数据,导致事务A出现了脏读的情况。
RU读未提交-insert操作引起的脏读下面实验过程是事务A读取了事务B插入后uncommitted未提交的数据,导致事务A出现了脏读的情况。
不可重复读RU读未提交-update操作引起的不可重复读下面实验过程是事务A读取了事务B修改后uncommitted未提交的数据,导致事务A出现了脏读的情况;
还验证了事务A读取了事务B修改后committed已经提交的数据,导致事务A出现了不可重复读的现象。
RU读未提交-delete操作引起的不可重复读下面实验过程是事务A读取了事务B删除后uncommitted未提交的数据,导致事务A出现了脏读的情况;
还验证了事务A读取了事务B删除后committed已经提交的数据,导致事务A出现了不可重复读的现象。
幻读RU读未提交-insert操作引起的幻读下面实验过程是事务A读取了事务B插入后uncommitted未提交的数据,导致事务A出现了脏读的情况;
还验证了事务A读取了事务B插入后committed已经提交的数据,导致事务A出现了幻读的现象。
这也是脏读和幻读的一个容易混淆的地方:两者同样都是insert操作,脏读是针对未提交的数据定义的,而幻读是针对已经提交的数据来定义的。
RC读已提交脏读-不会发生在读已提交事务隔离级别下,已经修复了脏读的问题,所以在该事务隔离级别下是不可能发生脏读的。
所以,下面我们只演示并验证在读已提交事务隔离级别下,不可重复读和幻读是如何发生的。实验过程详见下面的各个步骤。
不可重复读RC读已提交事务-update操作引起的不可重复读下面实验过程是事务A不能读取了事务B修改后uncommitted未提交的数据,从而避免了事务A出现脏读的情况;
还验证了事务A读取了事务B修改后committed已经提交的数据,导致事务A出现了不可重复读的现象:同样的查询条件在前后两次查询的结果集不一样。
RC读已提交-delete操作引起的不可重复读下面实验过程是事务A不能读取了事务B删除后uncommitted未提交的数据,从而避免了事务A出现脏读的情况;
还验证了事务A读取了事务B删除后committed已经提交的数据,导致事务A出现了不可重复读的现象:同样的查询条件在前后两次查询的结果集不一样。
幻读RC读已提交-insert操作引起的幻读下面实验过程是事务A不能读取到事务B插入后uncommitted未提交的数据,从而避免了事务A出现脏读的情况;
还验证了事务A读取了事务B插入后committed已经提交的数据,导致事务A出现了幻读的现象。
RR可重复读脏读-不会发生在可重复读事务隔离级别下,已经修复了脏读的问题,所以在该事务隔离级别下是不可能发生脏读的现象的。
不可重复-不会发生在可重复读事务隔离级别下,已经修复了不可重复读的问题,所以在该事务隔离级别下是不可能发生不可重复的现象的。
所以,下面我们只演示并验证在可重复读事务隔离级别下,幻读是如何发生的。实验过程详见下面的各个步骤。
幻读RR可重复读-insert操作引起的幻读-现象1-统计记录数不一致下面实验过程是事务A不能读取到事务B插入后uncommitted未提交的数据,从而避免了事务A出现脏读的情况;
还验证了事务A不能读取到事务B插入后committed已经提交的数据,进而避免了事务A出现不可从复读的情况;
还验证了事务A在当前读的情况下,发生的幻读现象。
RR可重复读-insert操作引起的幻读-现象2-更新后多出一行记录下面实验过程是事务A不能读取到事务B插入后uncommitted未提交的数据,从而避免了事务A出现脏读的情况;
还验证了事务A不能读取到事务B插入后committed已经提交的数据,进而避免了事务A出现不可重复读的情况;
还验证了事务A在使用和查询语句相同的条件更新数据后,再次使用相同的查询条件进行查询验证更新的结果,最后发现多了一条记录的现象,这个过程中涉及到了当前读的情况下,也就发生幻读的现象。
RR可重复读-insert操作引起的幻读-现象3-插入数据失败下面实验过程是事务A不能读取到事务B插入后uncommitted未提交的数据,从而避免了事务A出现脏读的情况;
还验证了事务A不能读取到事务B插入后committed已经提交的数据,进而避免了事务A出现不可重复读的情况;
还验证了事务A在使用和查询语句验证数据不存在后,向表中尝试插入数据,最后发现向插入的数据插入不成功提示主键冲突的错误信息,这个过程中也涉及到了当前读的情况,也就发生幻读的现象。
RR可重复读-insert操作引起的幻读-修复幻读的方式:采用当前读时加锁在RR可重复读事务隔离级别下,一致性的快照读是不会发生幻读现象的,只有在当前读的时候,才会发生幻读的现象。MySQL为了解决在当前读的时候发生幻读的现象,引入的间隙锁来解决这个问题。在当前读的时候,对读取的数据范围加上间隙锁,避免其他事务向当前事务读取的数据范围内插入任何操作,就可以避免幻读现象的发生。
什么是当前读?当前读的概念是指,事务在读取数据的时候,总是读取最新的已经提交的数据版本,这个就称为当前读。
我们常见的当前读的动作一般都发生在update修改操作的时候,我们要修改某一条数据,是要基于最新的已经提交的数据版本再进行修复,否则就会把其他事务已经提交的修复该覆盖掉了,导致其他事务更新的内容被我们给覆盖掉了,就是我们平时听到的更新丢失的现象。除了在update操作的时候发生当前度,以下几个动作也会发生当前读。
- select * from T where v = xx lock in share mode;
- select * from T where v = xx for update;
- insert into T (k v) values (xx xx); -- 插入数据的时候,要判断是否已经存在待插入的主键值,避免发生主键冲突,所以要读取最新的已经提交的数据来判断。此时要使用当前读来读取数据。
- update T set v = xx where id > xx; -- 修改数据的时候,如果有已经符合where条件的数据被其他事务插入并且已经提交,修改的时候,也要把这个被其他事务插入且提交的数据行也一并修改掉。避免出现这样的情况:漏掉一行数据没有修改成功,而其他符合条件的行都修改成功了。此时要使用当前读来读取数据。
- delete from T where v = xx; -- 删除数据的时候,如果有已经符合where条件的数据被其他事务插入并且已经提交,删除的时候,也要把这个被其他事务插入且提交的数据行也一并删除掉。避免出现这样的情况:漏掉一行数据没有删除成功,而其他符合条件的行都删除成功了。此时要使用当前读来读取数据。
下面就是一个在使用当前读的时候,增加上间隙锁来避免其他事务向当前事务查询的数据范围插入数据,进而避免幻读的发生。在当前事务还没有释放间隙锁之前,其他事务向间隙中插入数据的动作就会被阻塞,只有等到当前事务释放了间隙锁之后,其他事务才可以继续进行。示例如下:
注意:间隙锁锁定的是表中的索引上面数据的范围区间,和它有冲突的是想区间中插入数据这个插入的动作,间隙锁与间隙锁之间是可以叠加共存的,也就是说,同一个区间可以有两个或两个以上的间隙锁的存在,它们之间没有冲突。不像我们平时所说的行锁那样,和行锁有冲突的是同一行中的另外一个行锁。
Rerializable串行化在该事务隔离级别下,所有的事务都串行化执行,所以不会发生脏读、不可重复读、幻读的现象。但是在该事务隔离级别下,数据库的并发性是最低的,因为我们把数据库隔离级别设置的越高,它越安全,同时它的并发性就越底。
脏读-不会发生串行化下面,是如何修复脏读的呢?
串行化-update操作引起的脏读-修复脏读的方式:加锁阻塞修改 串行化-delete操作引起的脏读-修复脏读的方式:加锁阻塞删除 不可重复读-不会发生串行化-udpate操作引起的不可重复读-修复不可重复读的方式:加锁阻塞修改 串行化-delete操作引起的不可重复读-修复不可重复读的方式:加锁阻塞删除 幻读-不会发生串行化-insert操作引起的幻读-修复幻读的方式:加锁阻塞插入 串行化-insert操作引起的幻读-修复幻读的方式:加锁阻塞读取 总结- 脏读:其他事务修改、删除、插入的操作,并且还是未提交的操作,在你当前的事务中读取到了,称为脏读。
- 不可重复读:其他事务修改或删除的操作,并且已经提交的操作,在你当前的事务中读取到了,当前你前后两次读取的数据内容不一致,称为不可重复读。
- 幻读:其他事务插入的操作,并且已经提交的操作,在你当前的事务中读取到了,称为幻读。
其实其他事务插入且未提交的操作被你当前的事务读取到了也称为脏读。在SQL92标准中,Dirty read定义中的着重强调的是未提交的修改,这个修改可以是新增、也可以是删除、也可以说是普通的update修改语句。
- 修复脏读就用RC读已提交事务隔离级别。
- 修复不可重复读就使用RR可重复读事务隔离级别。
- 修复幻读就用串行化事务隔离级别。
在MySQL中的幻读只在当前读的情况下才会出现,在一致性快照读下面不会出现,一致性快照读之所以不会出现得益于MySQL的MVCC的机制。而修复当前读情况下面的幻读问题,使用LBCC间隙锁来避免幻读的发生,但是这个增加间隙锁的动作需要我们根据自己的业务逻辑手动的去增加,默认情况下,MySQL是不会自动给我们增加上间隙锁的。所以说MySQL在RR可重复读事务隔离级别下,是可以避免幻读的发生的。
关注我「程序猿集锦」,获取更多分享。