事务隔离级别精讲(为小白量身定做)

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 事务隔离级别精讲(为小白量身定做),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

看完本篇文章你能学到什么?

1、回滚点的使用

2、并发访问下事务产生的问题

3、掌握什么是脏读、不可重复读、幻读的概念,以及如何产生

4、通过设置不同的隔离级别来解决不同的问题。

5、串行化锁表原理


事务的基本概念请参考:如何跟女朋友解释清楚什么是事务?

一、事务的应用

事务很大程度上保障了我们业务操作的安全性。因此在很多安全性较高的场合我们都需要借助事务来帮我们完成。

1.1 MySQL的事务

MySQL中可以有两种方式进行事务的操作:

  • 1)手动提交事务
  • 2)自动提交事务(MySQL默认)

查看当前MySQL是否是自动提交事务:

show variables like 'autocommit';

在这里插入图片描述

No(1):开启自动提交事务(默认值)
OFF(0):关闭自动提交事务

  • 设置手动提交事务(关闭自动提交事务):
set autocommit=0;

在这里插入图片描述

set autocommit只是本次会话有用,下次会话又将变为自动提交事务。
如果需要永久生效,请到MySQL配置文件中增加:autocommit=0

  • 事务执行过程:
    在这里插入图片描述

1.2 演示手动提交事务

1.2.1 手动提交事务的SQL语句

功能 SQL语句
开启事务 start transaction
提交事务 commit
回滚事务 rollback

1.2.2 演示事务提交

模拟小刚给小美转账520元

  • 数据准备:
create table `user`(

	id int primary key auto_increment,
	name varchar(30),
	money double
);

insert into `user` values(1,'xiaogang',600);

insert into `user` values(2,'xiaomei',0);

小刚初始余额:600
小美初始余额:0

  • 操作如下代码:
-- 查询两个人的余额
select * from user;

-- 开启事务
start transaction;

-- 执行SQL操作(小刚扣钱),此时并没有真正写入到数据库(因为后续操作很有可能会有错误)
update user set money=money-520 where id=1;

-- 执行SQL操作(小美加钱),此时并没有真正写入到数据库(因为后续操作很有可能会有错误)
update user set money=money+520 where id=2;

-- 提交事务(确认无误,提交事务)
commit;

在这里插入图片描述

事务提交后查询最新数据,发现小刚的余额-520,剩余80,小美的余额+520,剩余520。

1.2.3 演示事务回滚

首先把小刚和小美的余额恢复到原始状态,小刚600元,小美0元。
在这里插入图片描述

  • 执行如下SQL,分析结果:
-- 查询小刚和小美的余额
select * from user;

-- 开启一个事务
start transaction;

-- 小刚扣钱,此时并没有真正写入到数据库(因为后续操作很有可能会有错误)
update user set money=money-520 where id=1;

-- 小美加钱,此时并没有真正写入到数据库(因为后续操作很有可能会有错误)
update user set money=money+520 where id=2;

-- 发现中途操作其他业务时,出现了错误,那么此次事务作废(之前的所有操作全部不会记录到数据库)
rollback;

-- 再次查询数据库,发现余额还是原来的
select * from user;

在这里插入图片描述

1.3 回滚点

  • 什么是回滚点?

在某些成功的操作完成之后,后续的操作有可能成功有可能失败,但是不管成功还是失败,前面操作都已经成功,可以在当前成功的位置设置一个回滚点。可以供后续失败操作返回到该位置,而不是回滚所有操作,这个点称之为回滚点。

  • 回滚点的操作语句
回滚点的操作语句 语句
设置回滚点 savepoint 名字
回到回滚点 rollback to 名字

我们来操作一段SQL,如下:

1)让小刚账号-10块钱

2) 让小美账号+10块钱

3)设置回滚点

4)让小刚账号-20块钱

5)让小美账号+20块钱

6)回滚到指定保存点

7)提交本次事务

-- 查询原始数据
select * from user;

-- 开启事务
start transaction;

-- 执行减钱操作
update user set money=money-10 where id=1;

-- 执行加钱操作
update user set money=money+10 where id=2;

-- 设置一个保存点,防止后续出现意外要回滚本次所有的操作
savepoint one;

-- 执行减钱操作
update user set money=money-10 where id=1;

-- 执行加钱操作
update user set money=money+10 where id=2;

-- 回滚到one保存点之前的状态
rollback to one;

-- 提交本次事务
commit;

-- 再次查询数据
select * from user;

在这里插入图片描述

设置回滚点可以让我们在失败的时候回到回滚点,而不是回到事务开启的时候。

二、事务的隔离级别

2.1 并发访问下事务产生的问题

当同时有多个用户在访问同一张表中的记录,每个用户在访问的时候都是一个单独的事务。

事务在操作时的理想状态是:事务之间不应该相互影响,实际应用的时候会引发下面三种问题
应该尽量避免这些问题的发生。通过数据库本身的功能去避免,设置不同的隔离级别。

  • 脏读: 一个事务(用户)读取到了另一个事务没有提交的数据
  • 不可重复读:一个事务多次读取同一条记录,出现读取数据不一致的情况。一般因为另一个事务更新了这条记录而引发的。
  • 幻读:在一次事务中,多次读取到的数据条数不一致(InnoDB存储引擎已经解决幻读问题),因此在MySQL的默认隔离级别中幻读已经被解决

四种隔离级别:

级别 名字 隔离级别 脏读 不可重复读 幻读 数据库默认隔离级别
1 读未提交 read uncommitted
2 读已提交 read committed Oracle和SQL Server
3 可重复读 repeatable read MySQL
4 串行化 serializable

四种隔离级别起的作用:

  1. Serializable (串行化): 可以避免所有事务产生的并发访问的问题 效率及其低下
  2. Repeatable read (可重复读):会引发幻读的问题(在InnoDB存储引擎已经解决)
  3. Read committed (读已提交):会引发不可重复读和幻读的问题
  4. Read uncommitted (读未提交): 所有事务中的并发访问问题都会发生
  • 查询全局事务隔离级别
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

mysql> 

MySQL默认的事务隔离级别为:REPEATABLE-READ(可重复读)

2.2 脏读

在并发情况下,一个事务读取到另一个事务没有提交的数据,这个数据称之为脏数据,此次读取也称为脏读。

我们知道,只有read uncommitted(读未提交)的隔离级别才会引发脏读。

  • 我们把当前事务的隔离级别改为read uncommitted(读未提交)。
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

记得需要退出当前客户端,重新打开

  • 重新打开客户端,查看当前事务隔离级别:
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)

mysql>

隔离级别已经更改为READ-UNCOMMITTED(读未提交)。

2.2.1 脏读演示

在窗口一执行转账操作,此时还未提交,窗口二执行查询,查询到了窗口一还未提交的脏数据。
在这里插入图片描述
脏读由事务隔离级别READ-UNCOMMITTED(读未提交)所引发,一个事务中读取到了其他事务还未提交的数据,因为数据还没提交,其他事务随时可能回滚。我们不能拿还未提交到数据库中的脏数据做数据运算处理,这样是非常不安全的。

2.2.2 解决脏读问题

我们可以通过调高MySQL的事务隔离级别,来解决脏读问题。

  • 将MySQL的事务隔离级别设置为read committed(读已提交):
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

mysql>
  • 再次查看MySQL的事务隔离级别:
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.00 sec)

mysql>

修改完事务隔离级别后记得要重启客户端(重新连接)

操作一段SQL,观察变化:

在这里插入图片描述

2.3 不可重复读

在同一个事务中的多次查询应该出现相同的结果,两次读取不能出现不同的结果。

隔离级别为read committed(读已提交)会引发不可重复读,我们在上面的演示中能够看出,小美窗口一次事务中多次查询到的数据不一致,这就是不可重复读。在一次事务中,我们应该保证多次查询的数据是一致的。

2.3.1 不可重复读演示

在窗口一执行转账操作,此时还未提交,窗口二执行查询,发现数据是正常的(还未被修改),此时窗口提交数据,窗口二再次执行查询,发现数据被修改了。在一次事务中多次查询的结果不一致。
在这里插入图片描述
不可重复读问题由read committed(读已提交)所引发,一个事务中,多次读取到的数据不一致(读取到了其他事务提交的数据)。Oracle和SQL Server数据库的默认隔离级别为读已提交,因此会引发不可重复读问题。

2.3.2 解决不可重复读

我们再次提高事务的隔离级别,来解决不可重复读问题。

  • 将当前MySQL的事务隔离级别设置为repeatable read(可重复读):
mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

注意重新启动MySQL客户端

  • 再次查看MySQL的事务隔离级别:
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

mysql>

操作如下SQL,观察变化:

在这里插入图片描述

MySQL的默认隔离级别为repeatable read(可重复读)。

2.4 幻读

幻读由事务隔离级别可重复读引发,在一个事务内读取到了别的事务插入的数据,由于”可重复读“的原因,查询不到那条数据,想插入那条数据发现提示”数据已存在”,但是又查询不到,就好像出现幻觉一样。

2.4.1 幻读演示

在窗口一和窗口二分别开启事务,并查询当前user表信息,都发现id为3的用户,窗口一率先插入id为3的用户,**并且提交。**此时窗口二再次查询数据表信息,发现依旧没有id为3的用户,于是想插入id为3的数据,发现报错(主键冲突),此时窗口二感觉很诡异,于是再次查询数据表信息,发现依旧没有id为3的用户,就好像出现幻觉一样,明明查询不到,但是又不让插入。
在这里插入图片描述

2.4.2 解决幻读

我们再次将事务隔离级别调高,来解决幻读问题。

  • 将当前MySQL的事务隔离级别设置为repeatable read(串行化):
mysql> set global transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

重启MySQL客户端

  • 再次查询MySQL的事务隔离级别:
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+
1 row in set, 1 warning (0.00 sec)

mysql>
  • 执行SQL,观察如下操作:

在这里插入图片描述

2.4.3 串行化解释

通过上面的演示,我们知道隔离级别为serializable(串行化)可以解决幻读问题,但是随之而然给我们带来的弊端非常明显,那就是并发性能极差。

在窗口一的事务中,一旦有更新操作(insert、update、delete)等, 那么会将整表都锁起来,不让其他客户端(事务)操作,而且是读写都不让!如果是在高并发情况下,串行化毋庸置疑会导致大量的锁等待,性能极低,因此实际生产中没有人会使用串行化的隔离级别。

其实我们通过名词就能知道,“串行化”明显和并行(并发)是冲突的,串行化要求的是”串行”,而我们我们实际开发则要求的是”并行”。

串行化的隔离级别在一个客户端(事务)执行任何的更新操作(insert、update、delete)等,其他客户端(事务),均不可对该表进行读写操作(记住,是读写均不能操作!)。

  • 我们再来演示一下:
    在这里插入图片描述

  • serializable隔离级别详细图解:
    在这里插入图片描述

三、总结

1)MySQL的默认隔离级别为:repeatable read(可重复读)

2)Oracle和SQL Server的默认隔离级别为:read committed(读已提交)

3)回滚点的概念:在一次事务中,操作的N多次数据库,但由于某次操作失误,需要rollback,但是一旦执行rollback,回滚此次事务的所有操作,因此我们可以在一次事务中,执行过若干次代码后,设置一个保存点,如果后续出现上面失误操作,可以回滚到指定的位置。

4)回滚点的指令:savepoint name

5)事务在并发访问下的安全问题(隔离级别)

6)脏读、不可重复读、幻读的基本概念

7)通过隔离级别来解决不同问题

8)牢记串行化给我们带来的问题(锁表)

好了,本篇就说到这里了,看完觉得有帮助的童鞋记得点赞!点赞!点赞!(重要的事情说三遍)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131822.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!