【MySQL】事务

1.1 事务的概述

1.1.1 什么是事务?

如果一个业务操作中多次访问了数据库,必须保证每条SQL语句都执行成功;如果其中由一条执行失败,那么所有已经执行过的代码必须回滚(撤销),回到没有执行前的状态,称为事务。简单来说,就是要么所有的SQL语句全部执行成功,要么全部失败。

1.1.2 事务的四大特性

事务特性

含义

原子性(Atomicity)

事务是工作的最小单元,整个工作单元要么全部执行成功,要么全部执行失败

一致性(Consistency)

事务执行前与执行后,数据库中数据应该保持相同的状态(数据总量不变)

隔离性(Isolation)

事务与事务之间不能互相影响,必须保持隔离性

持久性(Durability)

如果事务执行成功,对数据库的操作是持久的

1.1.3 事务提交的方式

  • 自动提交:MySQL 就是自动提交的 (一条 DML(增删改)语句会自动提交一次事务)
  • 手动提交:需要先开启事务,再提交(Oracle 是默认手动提交的)
  • 查看事务的默认提交方式:select @@autocommit; 1 代表自动提交,默认值,0 代表手动提交)
  • 修改默认提交方式:set @@autocommit = 0;1 代表自动提交,0 代表手动提交)

1.2 事务的应用场景说明

  • 转账的操作
drop database if exists db04;

create database db04;

use db04;

-- 创建数据表  
CREATE TABLE account (  
    id INT PRIMARY KEY AUTO_INCREMENT,  
    name VARCHAR(10),  
    money DOUBLE  
);  

-- 添加数据  
INSERT INTO account (name, money) VALUES ('a', 1000), ('b', 1000);
  • 转账需求:

模拟a给b转500元钱,一个转账的业务操作最少要执行下面的2条语句:

a账号-500

b账号+500

-- 转账操作

-- 有两条UPDATE的更新操作

-- a转500给b
update account set money=money-500 where name='a';

update account set money=money+500 where name='b';

-- 还原
update account set money=1000;

假设当a账号上-500元,服务器崩溃了。b的账号并没有+500元,数据就出现问题了。我们需要保证其中一条SQL语句出现问题,整个转账就算失败。只有两条SQL都成功了转账才算成功。这个时候就需要用到事务。

1.3 手动提交事务

1.3.1 SQL语句

功能

SQL语句

开启事务

start transaction/begin

提交事务

commit

回滚事务

rollback

1.3.2 使用过程

 1.3.3 事务提交

模拟a给b转500元钱(成功) 目前数据库数据如下:

  • 使用DOS控制台进入MySQL
  • 执行以下SQL语句: 1.开启事务, 2.xiaodong账号-500, 3.xiaobiao账号+500
  • 使用Navicat查看数据库:发现数据并没有改变
  • 在控制台执行commit提交任务:
  • 使用Navicat查看数据库:发现数据改变
mysql> use db04;
Database changed
mysql> select * from account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | a    |  1000 |
|  2 | b    |  1000 |
+----+------+-------+
2 rows in set (0.00 sec)

-- 执行具体操作

-- 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- a账号-500元
mysql> update account set money = money-500 where name = 'a';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- b账号+500元
mysql> update account set money = money+500 where name = 'b';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 提交事务
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | a    |   500 |
|  2 | b    |  1500 |
+----+------+-------+
2 rows in set (0.00 sec)

1.3.4 事务回滚

首先还原数据:

mysql> update account set money = 1000;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0

mysql> select * from account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | a    |  1000 |
|  2 | b    |  1000 |
+----+------+-------+
2 rows in set (0.00 sec)

模拟a给b转500元钱(失败):

  • 在控制台执行以下SQL语句:1.开启事务, 2.a账号-500
  • 使用Navicat查看数据库:发现数据并没有改变
  • 在控制台执行rollback回滚事务:
  • 使用Navicat查看数据库:发现数据没有改变
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update account set money = money-500 where name = 'a';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | a    |  1000 |
|  2 | b    |  1000 |
+----+------+-------+
2 rows in set (0.00 sec)

-- 回滚事务
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

-- 再次查询数据发现数据回到事务开始之前的状态
mysql> select * from account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | a    |  1000 |
|  2 | b    |  1000 |
+----+------+-------+
2 rows in set (0.00 sec)
  • 如果事务中SQL语句没有问题,那就commit提交事务,会对数据库数据的数据进行改变
  • 如果事务中SQL语句有问题,那就rollback回滚事务,会回退到开启事务时的状态

1.4 自动提交事务

MySQL默认每一条DML(增删改)语句都是一个单独的事务,每条语句都会自动开启一个事务,执行完毕自动提交事务,MySQL默认开始自动提交事务。

1.4.1 案例演示

  • 将金额重置为1000
  • 更新其中某一个账户
  • 使用Navicat查看数据库:发现数据已经改变

1.4.2 取消自动提交

  • 查看MySQL是否开启自动提交事务
-- 1表示自动提交(默认值),0表示手动提交
select @@autocommit;
  • 取消自动提交事务
set @@autocommit = 0;

1.5 事务原理

事务开启之后,所有的操作都会临时保存到事务日志中,事务日志只有在达到commit命令才会同步到数据表中,执行完commit或rollback都会清空事务日志(rollback,断开连接)。

 原理解释:

  • 如果没有开启事务,用户不适用日志文件,而是直接写到数据库
  • 如果查询,数据从表中查询出来以后,经过日志文件加工以后返回
  • 如果回滚,清除日志文件,不会写到数据库中

1.6 回滚点

1.6.1 什么是回滚点?

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

1.6.2 操作语句

回滚点的操作语句

语句

设置回滚点

savepoint 名字

回到回滚点

rollback to 名字

1.6.3 具体操作

  • 将数据还原到1000
  • 开启事务
  • 让a账号减2次钱,每次10块
  • 设置回滚点:savepoint p1;
  • 让a账号减2次钱,每次10块
  • 回到回滚点:rollback to p1;
  • 分析执行过程
-- 开启事务
begin;

-- a账号-10元
update account set money=money-10 where name='a';

-- a账号-10元
update account set money=money-10 where name='a';

-- 设置保存点
savepoint p1;

-- 查询账号信息
select * from account;

-- a账号-10元
update account set money=money-10 where name='a';

-- a账号-10元
update account set money=money-10 where name='a';

-- 回滚到指定的保存点
rollback to p1;

-- 查询账号信息
select * from account;

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

1.7 事务的隔离级别

1.7.1 并发访问的三个问题 

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

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

  • 脏读: 一个事务(用户)读取到了另一个事务没有提交的数据
  • 不可重复读:一个事务多次读取同一条记录,出现读取数据不一致的情况。一般因为另一个事务更新了这条记录而引发的
  • 幻读:在一次事务中,多次读取到的条数不一致

1.7.2 设置隔离级别

级别

名字

隔离级别

脏读

不可重复读

幻读

1

读未提交

read uncommitted

2

读已提交

read committed

3

可重复读

repeatable read

是/否

4

串行化

serializable

  • 隔离级别越高,安全性就越高,性能越低
  • 隔离级别越低,安全性就越低,性能越高
  • 作用:

1)Read uncommitted (读未提交): 简称RU隔离级别,所有事务中的并发访问问题都会发生,可以读取到其他事务没有提交的数据

2)Read committed (读已提交):简称RC隔离级别,会引发不可重复读和幻读的问题,读取的永远是其他事务提交的数据

3)Repeatable read (可重复读):简称RR隔离级别,会引发幻读的问题,一次事务读取到的同一行数据,永远是一样

4)Serializable (串行化): 可以避免所有事务产生的并发访问的问题 效率及其低下

  • 查询全局事务隔离级别
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
  • 设置全局事务隔离级别
set global transaction isolation level 隔离级别;		-- 服务器只要不关闭一直有效
Tips:修改隔离级别后需要重启会话,因为@@表示全局

1.7.4 脏读

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

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

  • 将MySQL的事务隔离级别设置为read uncommitted
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
  • 将数据还原:

  •  脏读演示:

session-01

session-02

begin;

begin;

select * from account where name='a';

update account set money=money-200 where name='a';

select * from account where name='a';

rollback;

观察变化: 

  •  解决脏读:将全局的隔离级别进行提升

1.7.5 不可重复读

  • 概念:在同一个事务中的多次差应该出现相同的结果,两次读取不能出现不同的结果。
  • 和脏读的区别:脏读是读取前一事务未提交的脏数据,不可重复读是从夫读取了前一事务已提交的数据,当两次读取的结果不同。

应用场景:比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,两次输出结果却不一致,导致文件和屏幕中的结果不同,银行工作人员就不知道以哪个为准了。

  • 不可重复读演示

将数据进行恢复,并关闭窗口重新登录。

update account set money=1000;

session-01

session-02

begin;

begin;

select * from account where name='a';

update account set money=money-200 where name='a';

select * from account where name='a';

rollback;

观察变化:

 两次查询输出的结果不同,到底哪次是对的?

  • 解决不可重复读:为了保证多次查询数据一致,必须使用repeatable read隔离级别
-- 设置隔离级别为repeatable read
set global transaction isolation level repeatable read;

1.7.6 幻读

  • 概念:一次事务多次读取到的条数不一致而引发的问题。

在InnoDB(暂时理解是MySQL)中幻读在很多地方都得到了解决,但在一些特殊的情况下,还是会引发幻读问题;

为什么有的情况下能解决,有的情况下解决不了?因为一次事务多次读取到的条数不一致会导致有很多情况发生!

  • 幻读解决情况-1

还原数据:

update account set money=1000;

-- 设置隔离级别为repeatable read
set global transaction isolation level repeatable read;

记得重启客户端

session-01

session-02

begin;

begin;

select * from account;

insert into account values(3,'c',1000);

commit;

select * from account;

// 查询发现还是两条,幻读问题达到解决

观察变化:

 查询发现还是两条,幻读问题达到解决。

  • 幻读解决情况2

还原数据

session-01

session-02

begin;

begin;

select sum(money) from account;

// 查询表中的总金额,2000

insert into account values(3,'c',1000);

commit;

select sum(money) from account;

// 再次查询总金额,仍是2000,幻读问题达到解决

观察变化:

 再次查询总金额,仍是2000,幻读问题达到解决。

  • 幻读问题出现情况-1

还原数据

session-01

session-02

begin;

begin;

select * from account;

insert into account values(3,'c',1000);

commit;

select * from account;

// 查询发现还是两条

update account set money=0;

// 发现修改记录为3条

select * from account;

// 再次查询记录发现变为了3条,出现幻读

观察变化:

  •  特殊情况

还原数据

session-01

session-02

begin;

begin;

select * from account;

select * from account;

insert into account values(3,'c',1000);

commit;

select * from account;

// 再次查询,依旧没有id为3的记录

insert into account values(3,'c',1000);

// 查询不到,但又插入不进去

观察变化:

Tips:严格意义来说,上述案例是MySQL的快照机制导致的,不能算幻读;关于幻读我们理解概念就行,即:两次读取到的条数不一致!这就是幻读

1.7.7 串行化

  • 概念:

想要彻底的解决幻读,那么我们必须再把隔离级别调高,数据库的最高隔离级别为串行化(serializable)。

串行化相当于锁表操作,即一个事务如果操作了某张表(增加、删除、修改),那么就不允许其他任何事务操作此表,也不允许查询,等第一个事务提交或者回滚之后才可以操作,这样做效率及其低下,因此一般不会采用serializable隔离级别。

  • 串行化演示
-- 还原数据
truncate account;

insert into account(name, money) values('a', 1000), ('b', 1000);

set global transaction isolation level serializable; 	-- 设置隔离级别为串行化

session-01

session-02

begin;

begin;

update account set money=money-500 where name='a';

select * from account;

// 查询记录发现卡住了

 在串行化隔离级别中,相当于锁表的操作,在一个事务对表进行任何的insert/update/delete等操作时,其他事务均不可对其进行操作;在读写上是串行的,并发能力极差;

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值