当前位置:编程学堂 > MySql的事务隔离级别的详细介绍(附代码)

MySql的事务隔离级别的详细介绍(附代码)

  • 发布:2023-10-06 11:04

本篇文章给大家带来的内容是关于MySql的事务隔离级别的详细介绍(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

一、事务的四大特性(ACID)

了解事务隔离级别之前不得不了解的事务的四大特性。

1、原子性(Atomicity)

事务开始后所有操作,要么全部做完,要么全部不做。事务是一个不可分割的整体。事务在执行过程中出错,会回滚到事务开始之前的状态,以此来保证事务的完整性。类似于原子在物理上的解释:指化学反应不可再分的基本微粒,原子在化学反应中不可分割 。

2、一致性(Consistency)

事务在开始和结束后,能保证数据库完整性约束的正确性即数据的完整性。比如经典的转账案例,A向B转账,我们必须保证A扣了钱,B一定能收到钱。个人理解类似于物理上的能量守恒。

3、隔离性(Isolation)

事务之间的完全隔离。比如A向一张银行卡转账,避免在同一时间过多的操作导致账户金额的缺损,所以在A转入结束之前是不允许其他针对此卡的操作的。

4、持久性(Durability)

事务的对数据的影响是永久性的。通俗的解释为事务完成后,对数据的操作都要进行落盘(持久化)。事务一旦完成就是不可逆的,在数据库的操作上表现为事务一旦完成就是无法回滚的。

二、事务并发问题

在互联网的大潮中,程序存在的价值早已不是在传统行业中为了帮人们解决一些复杂的业务逻辑。用户体验至上的互联网时代,代码就像西二旗地铁站码农的脚步一样,速度、速度、还是速度。当然也不能坐错了方向,本来想去西直门最后到了东直门(暂且理解为正确性吧)。相对于传统行业复杂的业务逻辑,互联网更注重并发带给程序的速度与激情。当然超速也是有代价的。在并发事务中,一不小心可怜的码农就要跑路了。

1、脏读

又称无效数据读出。一个事务读取另外一个事务还没有提交的数据叫脏读。

例如:事务T1修改了一行数据,但是还没有提交,这时候事务T2读取了被事务T1修改后的数据,之后事务T1因为某种原因Rollback了,那么事务T2读取的就是脏数据。

2、不可重复读

同一个事务中,多次读出的同一数据是不一致的。

例如:事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。

3、幻读

不好表述直接上例子吧:

在仓库管理中,管理员要给刚到的一批商品进入库管理,当然入库之前肯定是要查一下之前有没有入库记录,确保正确性。管理员A确保库中不存在该商品之后给该商品进行入库操作,假如这时管理员B因为手快将已将该商品进行了入库操作。这时管理员A发现该商品已经在库中。就像刚刚发生了幻读一样,本来不存在的东西,突然之间他就有了。

注:三种问题看似不太好理解,脏读侧重的是数据的正确性。不可重复度侧重的于对数据的修改,幻读侧重于数据的新增和删除。

三、MySql四种事务隔离级别

上一章节了解了高并发下对事务的影响。事务的四种隔离级别就是对以上三种问题的解决方案。

隔离级别脏读       不可重复度幻读     
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
可串行化(serializable)

四、sql演示四种隔离级别

mysql版本:5.6

存储引擎:InnoDB

工具:navicat

建表语句:

CREATE TABLE `tb_bank` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(16) COLLATE utf8_bin DEFAULT NULL,
  `account` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (1, '小明', 1000);
登录后复制

1、通过sql演示------read-uncommitted的脏读

(2)read-uncommit导致的脏读

所谓脏读就是说,两个事务,其中一个事务能读取到另一个事务未提交的数据。
场景:session1要转出200元,session2转入100元。基数为1000。顺利完成正确的结果应该是900元。但是我们假设session2转入因为某种原因事务回滚。这时正确的结果应该是800元。

演示步骤:
① 新建两个session(会话,在navicat中表现为两个查询窗口,在mysql命令行中也是两个窗口),分别执行

 select @@tx_isolation;//查询当前事务隔离级别
 set session transaction isolation level read uncommitted;//将事务隔离级别设置为 读未提交
登录后复制

② 两个session都开启事务

 start transaction;//开启事务
登录后复制

③ session1和session2:证明两个操作执行前账户余额为1000

 select * from tb_bank where id=1;//查询结果为1000
登录后复制

④ session2:此时假设session2的更新先执行。

update tb_bank set account = account + 100 where id=1;
登录后复制

⑤ session1:在session2 commit之前session1开始执行。

 select * from tb_bank where id=1;//查询结果:1100
登录后复制

⑥ session2:因为某种原因,转入失败,事务回滚。

 rollback;//事务回滚
 commit;//提交事务
登录后复制

⑦ 这时session1开始转出,并且session1觉得⑤中查询结果1100就是正确的数据。

 update tb_bank set account=1100-200 where id=1;
 commit;
登录后复制

⑧ session1 和 session2查询结果

 select * from tb_bank where id=1;//查询结果:900
登录后复制

这时我们发现因为session1的脏读造成了最终数据不一致。正确的结果应该为800;
到此我们怎么避免脏读呢,将事务的隔离性增加一个级别到read-commit

(2)read-commit解决脏读

重置数据,使数据恢复到account=1000

① 新建两个session,分别设置

 set session transaction isolation level read committed;//将隔离级别设置为 不可重复读
登录后复制

重复执行(1)中的②③④步

⑤ session1执行查询

 select * from tb_bank where id=1;//查询结果为1000,这说明 不可重复读 隔离级别有效的隔离了两个会话的事务。
登录后复制

这时我们发现,将事务的隔离升级为read-committed;后有效的隔离了两个事务,使得session1中的事务无法查询到session2中事务对数据的改动。有效的避免了脏读。

2、通过sql演示-----read-committed的不可重复读

(1)read-commit的不可重复读

重置数据,使数据恢复到account=1000

所谓的不可重复读就是说,一个事务不能读取到另一个未提交的事务的数据,但是可以读取到提交后的数据。这个时候就造成了两次读取的结果不一致了。所以说是不可重复读。
READ COMMITTED 隔离级别下,每次读取都会重新生成一个快照,所以每次快照都是最新的,也因此事务中每次SELECT也可以看到其它已commit事务所作的更改
场景:session1进行账户的查询,session2进行账户的转入100。
session1开启事务准备对账户进行查询然后更新,这时session2也对该账户开启了事务进行更新。正确的结果应该是在session1开启事务以后查询读到的结果应该是一样的。

① 新建两个session,分别设置

 set session transaction isolation level read committed;
登录后复制

② session1和session2分别开启事务

 start transaction;
登录后复制

③ session1第一次查询:

 select * from tb_bank where id=1;//查询结果:1000
登录后复制

④ session2进行更新:

 update tb_bank set account = account+100 where id=1;
 select * from tb_bank where id=1;//查询结果:1100
登录后复制

⑤ session1第二次查询:

 select * from tb_bank where id=1;//查询结果:1100。和③中查询结果对比,session1两次查询结果不一致。
登录后复制

查看查询结果可知,session1在开启事务期间发生重复读结果不一致,所以可以看到read commit事务隔离级别是不可重复读的。显然这种结果不是我们想要的。

(2)repeatable-read可重复读

重置数据,使数据恢复到account=1000

① 新建两个session,分别设置

 set session transaction isolation level repeatable read;
登录后复制

重复(1)中的②③④
⑤ session1第二次查询:

 select * from tb_bank where id=1;//查询结果为:1000
登录后复制

从结果可知,repeatable-read的隔离级别下,多次读取结果是不受其他事务影响的。是可重复读的。到这里产生了一个疑问,那session1在读到的结果中依然是session2更新前的结果,那session1中继续转入100能得到正确的1200的结果吗?
继续操作:
⑥ session1转入100:

update tb_bank set account=account+100 where id=1;
登录后复制

到这里感觉自己被骗了,锁,锁,锁。session1的更新语句被阻塞了。只有session2中的update语句commit之后,session1中才能继续执行。session的执行结果是1200,这时发现session1并不是用1000+100计算的,因为可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本)。insert、update和delete会更新版本号,是当前读(当前版本)。

3、通过sql演示-----repeatable-read的幻读

在业务逻辑中,通常我们先获取数据库中的数据,然后在业务中判断该条件是否符合自己的业务逻辑,如果是的话,那么就可以插入一部分数据。但是mysql的快照读可能在这个过程中会产生意想不到的结果。
场景模拟:
session1开启事务,先查询有没有小张的账户信息,没有的话就插入一条。这是session2也执行和session1同样的操作。

准备工作:插入两条数据

 INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (2, '小红', 800);
 INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (3, '小磊', 6000);
登录后复制

(1)repeatable-read的幻读

① 新建两个session都执行

 set session transaction isolation level repeatable read;
 start transaction;
 select * from tb_bank;//查询结果:(这一步很重要,直接决定了快照生成的时间)
登录后复制

结果都是:

INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (4, '小张', 8000); select * from tb_bank;

登录后复制

以上就是MySql的事务隔离级别的详细介绍(附代码)的详细内容,更多请关注其它相关文章!

相关文章