文章目录
看完本篇文章你能学到什么?
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 | 否 | 否 | 否 |
四种隔离级别起的作用:
Serializable
(串行化): 可以避免所有事务产生的并发访问的问题 效率及其低下Repeatable read
(可重复读):会引发幻读的问题(在InnoDB存储引擎已经解决)Read committed
(读已提交):会引发不可重复读和幻读的问题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)等,其他客户端(事务),均不可对该表进行读写操作(记住,是读写均不能操作!)。
三、总结
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