文章目录:
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 A、Session B, Session A 从一个表中 读取 了一个字段, 然后 Session B 在该表中 插入 了一些新的行。 之后, 如果 Session A 再次读取 同一个表, 就会多出几行。
我们可以在第二个连接所处的当前事务中再次插入id为3的记录。
上面的执行结果告诉我们,id为3的记录已经存在了。这也就是说在第一个连接中的那个事务已经向表中添加了这条记录、并且也已提交,所以记录必然存在了,但是在第二个连接中的事务却查询不到,但是也无法插入同id的数据,这就感觉好像出现了幻觉一样的数据。即幻读了。。。
|