数据库事务隔离级别的理解(以mysql为例)

数据库事务定义

一组逻辑操作单元,使数据从一种状态变换到另一种状态。简而言之,就是一组事务若包含多个对数据库的操作,则这多个操作要么全部执行,要么全部不执行(执行一半出现异常,则之前的所有执行操作全部回滚)。

事务的ACID属性

  1. 原子性(Atomicity)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  2. 一致性(Consistency)
    事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

  3. 隔离性(Isolation)
    事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  4. 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

数据库的并发问题

针对同一个表中的数据,多个事务同时操作某一张表时,可能出现如下的并发问题:

  • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
  • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新且已提交了该字段。之后, T1再次读取同一个字段, 值就不同了。
  • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入且已提交了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。

数据库的隔离级别

针对于数据库可能出现的三种并发问题,数据库提供了四种隔离级别,依次解决不同问题:
数据库的四种隔离级别

重点:不同隔离级别下的并发问题的演示:

前情提要:本次演示,通过命令行窗口登录两个不同的数据库用户,在两个会话连接都保持同一个隔离级别下同时开启事务,对同一张数据表进行操作,依次演示不同隔离级别下的并发问题,如有错误还望各位指出。
两个不同的用户登录同一个数据库

隔离级别一:read uncommitted(读未提交)

顾名思义:读未提交,就是本事务可以读到其他事务已经修改但还未提交的脏数据,此隔离级别下,会出现脏读、不可重复读、幻读

  • step1: 将两个会话同时修改隔离级别一
	# 设置当前会话连接的隔离级别
	set session transaction isolation level read uncommitted;  
	# 查看当前会话连接的隔离级别
	select @@tx_isolation;

隔离级别一

  • step2: 对同一个数据库中的同一个表进行操作

对test数据库下的account表进行操作测试:

  • step3: 两边同时开启事务,分别查询该表
    在这里插入图片描述

  • step4: 左边窗口对account表的某一行数据进行修改,但不提交数据,事务仍未结束
    在这里插入图片描述
    注意:此时左边窗口已经对account做了修改,但没结束事务,接下来让右边窗口查看该表中的数据。

  • step5: 右边窗口查看account表
    在这里插入图片描述
    查询结果发现,本来左边窗口还未提交的数据,右边窗口却已经读取到了,此时让左边窗口对修改了的数据进行事务回滚并结束事务。

  • step6: 左窗口回滚事务并结束,右窗口再次查看该数据表
    在这里插入图片描述
    神奇的事情发生了,左边窗口对事务进行了回滚后,右边的窗口在自己的事务对同一张表两次查询中,查询出来的结果不一致。即出现了脏读问题
    既然脏读问题已出现,那么不可重复读和幻读问题同样也会存在,第一个隔离级别的演示结束。

隔离级别二:read committed(读已提交)

顾名思义:读已提交,就是本事务可以读到其他事务已经修改且提交的数据,未提交的数据无法读取到。此隔离级别下,避免了脏读、但会出现不可重复读、幻读

  • step1: 设置两个会话连接的隔离级别为read committed;
    注意:测试新的隔离级别时,要注意将两边窗口的上一个事务先提交,即执行commit指令
    隔离级别二

  • step2: 两边同时开启事务
    开启事务

  • step3: 左边窗口在事务中对account表中的一行数据进行修改,但不提交
    修改表中数据但不提交

  • step4: 右边窗口查看account表
    在这里插入图片描述
    可以看到,虽然左边窗口的事务已经成功执行了update操作,但因为还没提交数据。所以右边窗口在同一个事务的两次查询中得到的查询结果是一致的,即解决了脏读问题

  • step5: 左边窗口提交修改的数据,结束事务
    提交修改

  • step6: 右边窗口再次查看account表
    在这里插入图片描述
    此时可以看出,左边窗口提交数据后结束了事务。但右边窗口的事务仍在继续,且此时再度查询该表,发现同一个事务中的前后两次同一张表查询结果出现了不一致,此即为所谓的不可重复读
    那么,不可重复读现象已经出现,幻读也会出现。所以该隔离级别只解决了幻读,隔离级别二演示结束。

补充:该隔离级别是日常生活中常见的事务隔离级别,不可重复读是我们开发可以接受的情况,比如最常见的秒杀,我们在连接上商家的服务器后进行秒杀时,会发现前后看到的库存量不一致,这就是不可重复读的具体体现。所以Oracle的默认隔离级别为read committed。


隔离级别三:repeatable read(可重复读)

顾名思义:可重复度,就是本事务在执行过程中,即使其他事务对同一张表进行了修改且提交了数据,在本事务未来结束前,对同一张表的查询结果始终保持一致,实现了对该表的可重复读。此隔离级别下,避免了脏读、不可重复读,但会出现幻读

  • step1: 两个会话连接的隔离级别设置为repeatable read,同时都开启事务
    隔离级别三

  • step2: 左边窗口对account中的某一行数据进行修改,但不提交
    在这里插入图片描述

  • step3: 右边窗口查看account表
    在这里插入图片描述
    可以看出,此时左边窗口的事务还未提交,因此右边窗口在同一个事务的多次查询中,并未读取到左窗口未提交的数据,即避免了脏读

  • step4: 左边窗口提交数据,结束该事务
    在这里插入图片描述

  • step5: 右边窗口再次查看account表
    在这里插入图片描述
    在这里插入图片描述

此时可以看出,左边窗口虽然成功提交了事务,数据库实际也得到了修改。但由于右边窗口的事务仍在继续,因此多次对account表的查询结果仍然保持一致,不受到其他事务提交的修改数据的影响,此即为避免了不可重复读

补充:mysql的默认隔离级别即为repeatable read。

  • step6: 左边窗口开启事务,向account表插入一条数据,提交事务
    在这里插入图片描述

  • step7: 右边窗口再次查看account表
    在这里插入图片描述
    此时发现,左边提交了事务后,数据表确实是插入了一行新数据,但右边在同一个事务中,前后却查询不出行数的变化,注意,此时并不是因为出现了幻读,而是因为在该隔离级别下,不可重复读问题被解决了!所以查询结果保持了一致!
    下面的演示,才是幻读的演示。

  • step8: 右边窗口对account表进行批量修改,查看受影响行数
    在这里插入图片描述
    右边的窗口中,明明查询出来的结果是五行数据,但在对整张表进行批量修改时,却是六行数据受到了影响,这就是幻读的体现。同时,我们可以试下,如果只对最新一行的数据进行修改,是否会成功。

  • step9: 右边窗口对account表的最后一行数据进行修改,查看是否成功
    在这里插入图片描述
    可以看出,同样是成功的。即虽然没能查询出新插入的数据,但在做增删改时,仍然是可以对该新插入的数据进行操作的。
    因此,该隔离级别下, 避免了脏读,不可重复读,但没能避免幻读。

说说两个结论,区分不可重复读和幻读:
  1. 解决不可重复读,是在开启事务后,先查询某个表后,对该表的结果进行了保存;此后在该事务中,有关此次表的查询,都是以副本表来查出结果的。

演示:
假如开启事务后,A事务没有先对account表进行查询结果保存做副本,而B事务又对account表进行了新数据的插入,之后A事务再查询该account表,是可以直接看到新插入的数据的。

  • step1: 两边都开始事务,左边查询行数,右边不查询(以防止留下结果副本)
    在这里插入图片描述

  • step2: 左窗口的事务直接进行新数据的插入后提交
    在这里插入图片描述

  • step3: 右窗口查看account表
    在这里插入图片描述
    由图可以看出,由于右边的事务在开启后,没有先对account表进行查询,因此没有留下查询结果副本。等到左边的事务提交了对account表的插入修改后,右边在原事务中第一次查询该account表时,会直接查看到新插入的数据,即结论1得证。

  1. 幻读,是本事务先对某表进行查询,之后其他事务对该表插入数据后提交,然后本事务再次对该表进行查询时,仍无法查看到新插入的数据(受结论1的影响),但在执行增删改操作时,虽然查询出的结果仍然没有新插入的数据,但增删改由于需要对底层的数据做修改了,因此会出现新数据受影响的提示,修改完再次查询该表时,会重新从数据库获取最新的数据情况,此时就可以看到被修改了的新插入的数据行了。

隔离级别四:serializable(串行化)

顾名思义:串行化,类似于java中多线程的加锁机制,多个事务操作同一个数据块时,会进行加锁,将并行操作转化为串行操作,那么,没有了并行操作,自然就不存在并发问题,所以此隔离级别下,避免了脏读、不可重复读和幻读,但缺点是效率低下,无法实现高并发。

  • step1: 两个会话连接同时设置隔离级别为serializable,且同时开启事务
    隔离级别四

  • step2: 左边窗口对account进行查询,然后右窗口也对account进行查询
    在这里插入图片描述

  • step3: 左边窗口对account的某一行数据进行修改
    在这里插入图片描述
    注意!!!此时博主已经是按了回车键执行update操作的,结果该事务是进入了阻塞状态,无法进行修改,所以命令行一直没有往下显示提示信息。阻塞的原因是还有其他的事务(右边窗口的事务一开始对该表进行了查询操作)在操作这张表,所以对该表的增删改操作,必须等待也在操作这张表的其他事务结束后,才可以进行操作。

  • step4: 右边窗口结束事务
    在这里插入图片描述
    右边窗口commit了事务后,左边窗口原本在阻塞状态的update操作就可以顺利往下执行了。这就是串行化,避免了数据库的所有并发问题,当然带来的缺陷也显而易见,效率那叫一个低下啊。

总结:

serialiable隔离级别下,多个事务同时操作同个表,除了查询可以同时进行,增删改必须等其他的事务都结束后,才可以进行增删改(不分哪个事务操作该表先后)。




以上就是我在学习数据库事务时的演示,如有哪些不足欢迎各位好兄弟指出。对本文有任何疑问,欢迎评论区留言指出,最后祝大家学习愉快~
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值