MySQL基础9——MySQL事务
本文最后更新于 513 天前,如有失效请评论区留言。

事务简介

事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。

例如常见的银行转账:转账要么成功,要么失败,不管成功还是失败,转账前后银行里的总账不会改变。

image-20240802154705180

默认MySQL的事务是自动提交的,当执行一条DML语句时,MySQL会立即隐式提交事务。如果需要把若干操作放到一个事务内,就需要手动开启事务,手动提交事务,如果出现异常需要手动回滚。接下来我们通过一个转账案例来学习跟事务相关的一些操作,并熟悉两种事务的提交方式。

事务操作

事务操作——修改事务提交方式

首先准备数据库表环境

create table account(
    id int auto_increment primary key ,
    name varchar(10),
    money int
);
insert into account() values(null,'张三',2000),(null,'李四',2000);

恢复数据

update account set money = 2000 where name = '张三' or name = '李四';

查询张三账户的余额,将张三账户的余额减少1000,将李四账户的余额增加1000

select * from account;
update account  set money = money - 1000 where name = '张三';
update account  set money = money + 1000 where name = '李四';

image-20240805112509614

如果李四入账出现了错误,就会造成事故。上述步骤必须全部成功,或者全部失败,就需要使用到MySQL的事务特性。我们先来看一下事务的相关操作。

查看/设置MySQL事务提交方式

  • 默认为1,自动提交
  • 将当前会话的事务提交方式改为手动提交。
select @@autocommit;
set @@autocommit = 0;

提交事务

commit;

接下来通过手动提交事务的方式提交张三李四的转账。

update account  set money = money - 1000 where name = '张三';
update account  set money = money + 1000 where name = '李四';

打开account表,发现表格内容并没有更新。

image-20240805114741074

注意,如果用select语句查询,查询到的结果已经更新,实际因为我们没有提交,这里的结果只是一个临时结果。

image-20240805115025342

提交事务,查看表格内容已经修改。

commit;

image-20240805115124112

回滚事务

rollback

在执行事务个过程中出现了错误,可以通过回滚事务,来取消对表数据的修改。

  • 通过添加一行不是sql代码的内容,运行如下命令时就会发生报错。
update account  set money = money - 1000 where name = '张三';
程序报错...
update account  set money = money + 1000 where name = '李四';

image-20240805125004815

此时事务部分语句执行成功,部分语句执行失败,我们通过回滚事务,取消操作。

rollback;

image-20240805125148288

通过这个例子可以看到,在事务执行成功时通过commit提交事务,在事务执行出错时可以通过rollback滚滚事务,来保证数据的完整性和一致性。

事务操作——不修改事务提交方式

如果不修改事务的提交方式,我们需要多执行一步开启事务的命令。

1、开启事务
start transaction 或者 begin
2、提交事务
commit
3、回滚事务
rollback

恢复数据

set @@autocommit = 1;
update account set money = 2000 where name = '张三' or name = '李四';

执行并提交事务

start transaction;
update account  set money = money - 1000 where name = '张三';
update account  set money = money + 1000 where name = '李四';
commit;

执行并回滚事务

start transaction;
update account  set money = money - 1000 where name = '张三';
程序报错...
update account  set money = money + 1000 where name = '李四';
rollback;

事务的四大特性

事务具有四个特效,分别是原子性,一致性,隔离性,持久性。

  • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
  • 一致性(Consistency):事务完成时,必须使事务执行前后的数据保持一致。
  • 隔离性(Isolation):数据库系统提供了隔离机制,保证事务在不受外部并发操作影响到独立环境下运行。
  • 持久性(Durability):事务一旦提交或者回滚,它对数据库中的数据的改变就是永久的。

并发事务问题

A事务和B事务同时操作某一个数据库,甚至于同时操作一张表时,会引发以下问题:

  • 脏读:一个事务读到另外一个事务还没有提交的数据;
  • 不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同;
  • 幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了一个幻影。

接下来我们来举例说明并发事务问题发生的场景。

脏读:

  • 事务A执行了一个修改但是没有commit,而事务B读取到了这条数据

image-20240805132106425

不可重复读:

  • 在事务A的两次查询之间,事务B进行了一次update操作,导致事务A两次查询到的内容不一致。

image-20240805132317497

幻读:

  • 事务A执行insert操作之前,事务B先进行了一个insert,导致事务A执行失败,事务A再次查询还是没有该记录,造成了幻觉。

image-20240805132600689

为解决并发事务问题,MySQL提供了事务隔离级别的功能。

事务隔离级别

MySQL的事务隔离级别有 Read uncommitted(读未提交),Read committed(读已提交),Repeatable Read(默认,可重复读), Serializable(串行化)四种,Repeatable Read 是MySQL数据库的默认隔离级别。下图说明四种隔离级别中会产生的并发问题(✓会发送,×不会发生)。数据的隔离级别越高,性能就越低。。

image-20240805133331321

查看事务的隔离级别

select @@transaction_isolation;

设置事物的隔离级别

  • session针对当前客户端有效,global针对所有会话有效;
语法:
set [session | global] transaction isolation level {read uncommited | read commited | repeatable read | serializable};
示例:
set session transaction isolation level read uncommitted;

脏读问题

我们通过一个案例来说明如何解决脏读问题。脏读问题只会出现在read uncommited这个事务级别,我们先把MySQL会话1的事务级别设置为read uncommited。

mysql> set session transaction isolation level  read uncommitted ;

同时打开两个会话终端,连接MySQL数据库。

image-20240805141919456

在会话1中查看原始数据

mysql-1> select * from account;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 张三   |  2000 |
|  2 | 李四   |  2000 |
+----+--------+-------+
2 rows in set (0.00 sec)

在会话1两次select表数据的操作之间,在会话2中进行了一次update(这个更新操作还未提交),最终导致了会话1两次select的数据不一致,这就是脏读。

mysql-1> start transaction;
mysql-2> start transaction;
mysql-1> select * from account;
mysql-2> update account set money=money-1000 where name = '张三';
mysql-1> select * from account;

image-20240805142908007

把隔离级别改为read commit,再次进行测试。

在会话1步骤4和步骤6的select之间,通过会话2执行了一次update但是并未commit,结果是步骤4和步骤6的查询结果一致。在会话2通过步骤7进行事务commit,通过会话1再次查询,可以看到表中的数据已经发生改变。

mysql-1> set session transaction isolation level  read committed ;
mysql-1> start transaction;
mysql-2> start transaction;
mysql-1> select * from account;
mysql-2> update account set money=money-1000 where name = '张三';
mysql-1> select * from account;
mysql-2> commit;
mysql-1> select * from account;

image-20240805143709299

read commit 跟 read uncommit 相比,read commit 解决了脏读问题。

不可重复读问题

在上面的示例中,步骤6和步骤8的两次查询,获得了不一样的数据,这就是一个不可重复读问题。我们可以把隔离级别再往上提升到 repeatable read来解决这个问题。

  • 如图所示,在会话1的一个事务中的步骤3和步骤7之间,通过会话2进行了一次完整的事务提交,但是步骤7查询到的数据跟步骤3是一致的,这就是可重复读。前提是同一个事务下可重复读。我们通过步骤8进行会话1到事务提交,通过步骤9再次进行查询,此时看到表中的数据已经更新了。
mysql-1> set session transaction isolation level repeatable read;
mysql-1> start transaction;
mysql-1> select * from account;
mysql-2> start transaction;
mysql-2> update account set money=money+1000 where name = '张三';
mysql-2> commit;
mysql-1> select * from account;
mysql-1> commit;
mysql-1> select * from account;
mysql-1> start transaction;

image-20240805144829912

repeatable read 隔离级别已经解决了不可重复读的问题。

幻读

我们来看一下在repeatable read中的幻读问题。在步骤3和步骤6之间,我们通过会话2进行了一次事务的提交,新增了id=3的用户,由于repeatable read 可重复读的特性,在步骤6的查询中并没有查到id=3的用户存在,但在执行步骤7的id=3的数据插入时出现了报错,可通过步骤8再次查询还是没有看到数据,这就是幻读现象。

mysql-1> start transaction;
mysql-2>  start transaction;
mysql-1> select * from account;
mysql-2> insert into account(id,name,money) values(3,'王五',2000);
mysql-2> commit;
mysql-1> select * from account;
mysql-1> insert into account(id,name,money) values(3,'老六',2000);
mysql-1> select * from account;

image-20240805151114071

为了解决幻读现象,我们可以把隔离级别设置为serializable串行化。

  • 当隔离级别设置为serializable时,先执行步骤3对account表进行查询,然后执行步骤5的插入。因为检测到会话1的步骤3在对account表进行操作,所以步骤5被阻塞。
mysql-1> set session transaction isolation level serializable;
mysql-1> start transaction;
mysql-1> select * from account;
mysql-2> start transaction;
mysql-2> insert into account(id,name,money) values(4,'王老五',2000);

image-20240805152054146

此时在会话1中通过步骤5进行insert,可以正常插入数据,当会话1commit成功后,会话2中弹出步骤7的报错信息。

mysql-1> start transaction;
mysql-1> select * from account;
mysql-2> start transaction;
mysql-2> insert into account(id,name,money) values(4,'王老五',2000);
mysql-1> insert into account(id,name,money) values(4,'王老五',2000);
mysql-1> commit;

image-20240805153353459

串行化可以规避所有的并行问题,安全性最高,但是性能最低,为了效率和安全的平衡,一般使用默认的隔离级别即可。

版权声明:除特殊说明,博客文章均为cuckooyang原创,依据CC BY-SA 4.0许可证进行授权,转载请附上出处链接及本声明。 | 博客订阅:RSS | 广告招租:留言板 | 博客VPS |
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇