MySQL高级篇——事务的隔离级别与简单应用

Posted 张起灵-小哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL高级篇——事务的隔离级别与简单应用相关的知识,希望对你有一定的参考价值。

文章目录:

1.数据并发所存在的问题

1.1 脏写

1.2 脏读

1.3 不可重复读

1.4 幻读

2.SQL中事务的隔离级别

3.案例实操

3.1 查看与修改MySQL的隔离级别

3.2 读未提交-举例

3.3 读已提交-举例

3.4 可重复读-举例

3.5 幻读-举例


1.数据并发所存在的问题

针对事务的隔离性和并发性,我们怎么做取舍呢?先看一下访问相同数据的事务在 不保证串行执行 (也就是执行完一个再执行另一个)的情况下可能会出现哪些问题:

1.1 脏写

对于两个事务 Session A Session B ,如果事务 Session A 修改了 另一个 未提交 事务 Session B 修改过 的数据,那就意味着发生了 脏写。

1.2 脏读

对于两个事务 Session A Session B Session A 读取 了已经被 Session B 更新 但还 没有被提交 的字段。之后若 Session B 回滚 Session A 读取 的内容就是 临时且无效 的。 Session A Session B 各开启了一个事务, Session B 中的事务先将 studentno 列为 1 的记录的 name 列更新为' 张三 ' ,然后 Session A 中的事务再去查询这条 studentno 1 的记录,如果读到列 name 的值为 ' 张三 ' ,而Session B中的事务稍后进行了回滚,那么 Session A 中的事务相当于读到了一个不存在的数据,这种现象就称之为 脏读

1.3 不可重复读

对于两个事务 Session A Session B Session A 读取 了一个字段,然后 Session B 更新 了该字段。 之后 Session A 再次读取 同一个字段, 值就不同 了。那就意味着发生了不可重复读。 我们在 Session B 中提交了几个 隐式事务 (注意是隐式事务,意味着语句结束事务就提交了),这些事务都修改了studentno 列为 1 的记录的列 name 的值,每次事务提交之后,如果 Session A 中的事务都可以查看到最新的值,这种现象也被称之为 不可重复读

1.4 幻读

对于两个事务 Session A Session B, Session A 从一个表中 读取 了一个字段 , 然后 Session B 在该表中 插入 了一些新的行。 之后 , 如果 Session A 再次读取 同一个表 , 就会多出几行。那就意味着发生了幻读。 Session A 中的事务先根据条件 studentno > 0 这个条件查询表 student ,得到了 name 列值为 ' 张三 ' 的记录;之后Session B 中提交了一个 隐式事务 ,该事务向表 student 中插入了一条新记录;之后 Session A 中的事务再根据相同的条件 studentno > 0 查询表 student ,得到的结果集中包含 Session B 中的事务新插入的那条记 录,这种现象也被称之为 幻读 。我们把新插入的那些记录称之为 幻影记录

2.SQL中事务的隔离级别

mysql 是一个 客户端/服务器 架构的软件,对于同一个服务器来说,可以有若干个客户端与之连接,每个客户端与服务器连接上之后,就可以称为一个会话( Session )。每个客户端都可以在自己的会话中向服务器发出请求语句,一个请求语句可能是某个事务的一部分,也就是对于服务器来说可能同时处理多个事务。事务有 隔离性 的特性,理论上在某个事务 对某个数据进行访问 时,其他事务应该进行 排队 ,当该事务提交之后,其他事务才可以继续访问这个数据。但是这样对 性能影响太大 ,我们既想保持事务的隔离性,又想让服务器在处理访问同一数据的多个事务时 性能尽量高些 ,那就看二者如何权衡取舍了。
上面介绍了几种并发事务执行过程中可能遇到的一些问题,这些问题有轻重缓急之分,我们给这些问题按照严重性来排一下序:
脏写 > 脏读 > 不可重复读 > 幻读
我们愿意舍弃一部分隔离性来换取一部分性能在这里就体现在:设立一些隔离级别,隔离级别越低,并发问题发生的就越多。 SQL 标准 中设立了 4 隔离级别 READ UNCOMMITTED :读未提交,在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。 READ COMMITTED :读已提交,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这是大多数数据库系统的默认隔离级别(Oracle 默认的)。可以避免脏读,但不可重复读、幻读问题仍然存在。 REPEATABLE READ :可重复读,事务 A 在读到一条数据之后,此时事务 B 对该数据进行了修改并提交,那么事务A 再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读,但幻读问题仍然存在。这是MySQL 的默认隔离级别。 SERIALIZABLE :可串行化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避免脏读、不可重复读和幻读。 SQL标准 中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:

不同的隔离级别有不同的现象,并有不同的锁和并发机制,隔离级别越高,数据库的并发性能就越差, 4种事务隔离级别与并发性能的关系如下:


3.案例实操

3.1 查看与修改MySQL的隔离级别

查看MySQL的默认隔离级别,为REPEATABLE READ。

关于如何设置数据库的隔离级别,参考如下代码:👇👇👇 

SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL 隔离级别; 
#其中,隔离级别格式: 
> READ UNCOMMITTED 
> READ COMMITTED 
> REPEATABLE READ 
> SERIALIZABLE

#或者(推荐下面这种)

SET [GLOBAL|SESSION] TRANSACTION_ISOLATION = '隔离级别' 
#其中,隔离级别格式: 
> READ-UNCOMMITTED 
> READ-COMMITTED 
> REPEATABLE-READ 
> SERIALIZABLE

下面,我们建表,用作下面的案例测试。 

3.2 读未提交-举例

首先我们开启两个连接(这里均在Linux下演示),这两个连接中都将MySQL的隔离级别修改为读未提交。

此时,我们来了一个需求,需要将张三账户中的100元钱转账给李四,下面这样做:👇👇👇  我们在第一个连接中完成,首先begin显式的开启一个事务,两次update之后select可以看到转账成功了。但是此时第一个连接关于这个事务还没有进行commit提交。

上面在第一个连接中看到转账成功了,下面我们到第二个连接中看一下结果。

这里居然惊讶的看到了转账之后的数据(张三0、李四100),但是第一个连接还没有提交啊。。。

也即:在读未提交这种隔离级别下,所有事务都可以看到其他未提交事务的执行结果。

之后假如说李四这100元钱他不想要了,想再转给张三,那么我们需要在第二个连接中进行相应的update操作,但是一执行发现卡在这里了。(并不是因为数据库服务器连接问题,而是第一个连接中的那个事务还没有commit提交,所以这里需要等待第一个连接提交之后才可以继续进行DML操作)

但我此时不进行提交,我直接在第一个连接中进行rollback回滚,回滚也就意味着事务结束了。 (回滚之后的select是在下面第二张图执行之后查询的)

当回滚之后,第二个连接可以继续执行了,此时将李四的100转给张三。两次update之后select,出问题啦。。。张三200,李四-100???

这其实就是因为在第一个连接中进行了rollback回滚(张三100、李四0),此时这个数据在读未提交情况下是可以被第二个连接读到的,在此基础上,两次update导致李四又被扣了100,张三又多了100,所以李四0-100 = -100  张三 100 + 100 = 200。 (也即读未提交情况下存在脏读问题)

3.3 读已提交-举例

首先将上面的account表中数据清空,仍然向表中插入与上面案例相同的两条数据(张三100、李四0),进行下面的读已提交举例。

仍然是开启两个MySQL连接做测试。

下面在两个连接中,分别设置隔离级别为 读已提交。 

下面首先在第二个连接中 begin 开启一个事务,将张三的存款扣50元,那么此时在当前连接中肯定是可以select到正确的数据(张三50、李四0)。

然后,我们到第一个连接中,也开启一个事务,(如果是读未提交了话,由于第二个连接并没有commit提交,此时第一个连接就会读取到 张三50、李四0)。

但是此时隔离级别是读已提交,还是如此嘛?(如下可以看到此时已经不会读取到另一个事务未提交的数据了,也即在读已提交的情况下,解决了脏读问题)

下面在第二个连接中,把事务提交,select查询。回到第一个连接中再次查询,数据肯定也是正确的。

3.4 可重复读-举例

此时还以上面案例的数据为基准(张三50、李四0),来做测试,先将两个连接中的隔离级别修改为可重复读。

在第一个连接中开启一个事务, 然后将张三的存款扣10元钱,当前连接肯定可以select到正确的数据。

下面在第二个连接中也开启一个事务,此时读取到50是因为第一个事务还未提交数据,那么是不能被第二个连接中的事务读取到的。

然后我们在第一个连接中将事务提交,那么第一个连接中再次读取肯定还是正确数据(张三40、李四0)。

那么在第二个连接中是怎么样的呢?它能不能读取到正确数据(张三40、李四0)?

下面的实例表明:读取到的还是和第一次一样(张三50、李四0),这就达到了可重复读的要求,也就是事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。

下面,我们在第二个连接中将事务提交,再次读取,此时两个连接事务均已结束,所以肯定都会读取到正确数据了(张三40、李四0)。

3.5 幻读-举例

此时还基于可重复读这种隔离级别下,演示一下幻读的情况。   先在第一个连接中开启一个事务,然后插入一条id为3的数据。

然后我们回到第二个连接中,也开启一个事务,执行如下操作。

可以看到,id为3的记录为0(也即没有这条记录),因为第一个连接中的事务还未提交,这里自然读取不到。

下面将第一个连接中的事务提交,回到第二个连接中再次读取。

通过上面在第二个连接中的执行结果来看,按理说第一个连接中的事务已经提交,这里为什么还读取不到呢?  这里其实就出现了幻读的情况。

对于两个事务Session ASession B, Session A 从一个表中 读取 了一个字段, 然后 Session B 在该表中 插入 了一些新的行。 之后, 如果 Session A 再次读取 同一个表, 就会多出几行。

我们可以在第二个连接所处的当前事务中再次插入id为3的记录。

上面的执行结果告诉我们,id为3的记录已经存在了。这也就是说在第一个连接中的那个事务已经向表中添加了这条记录、并且也已提交,所以记录必然存在了,但是在第二个连接中的事务却查询不到,但是也无法插入同id的数据,这就感觉好像出现了幻觉一样的数据。即幻读了。。。

以上是关于MySQL高级篇——事务的隔离级别与简单应用的主要内容,如果未能解决你的问题,请参考以下文章

MySQL高级 之 事务(ACID特性 与 隔离级别)

重新整理 mysql 基础篇————— 事务隔离级别[四]

MySQL高级——锁与事务

MySQL事务篇:ACID原则事务隔离级别及事务机制原理剖析

Mysql原理篇之事务隔离级别和MVCC--13

Mysql基础篇之事务隔离---03