MySQL事务

1. 简介

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

MySQL数据库事务是默认自动提交的。也就是说,当执行一条DML语句,MySQL会立即隐式的提交事务。

image-20240614181850608


2. 事务案例

2.1 数据准备

新建数据库transaction_study

新建account表。

create table account(
    id int auto_increment primary key comment '主键ID',
    name varchar(10) comment '姓名',
    money int comment '余额'
) comment '账户表';
insert into account(id,name,money) values(null,'Linda',2000),(null,'Tom',2000);

image-20240614183252580

2.2 转账操作

正常操作

# 1.查询Linda账户余额
select money from account where name='Linda';
# 2.Linda账户余额-1000
update account set money=money-1000 where name='Linda';
# 3.Tom账户+1000
update account set money=money+1000 where name='Tom';

image-20240614183227355

模拟异常情况

Linda向Tom转账1000,当Linda账户余额减掉1000后,发生异常。

# 1.查询Linda账户余额
select money from account where name='Linda';
# 2.Linda账户余额-1000
update account set money=money-1000 where name='Linda';

select 模拟异常

# 3.Tom账户+1000
update account set money=money+1000 where name='Tom';

只要在sql中增加select 模拟异常即可,因为这不是正确的sql语句,所以就相当于错误的存在。

然后将这部分sql选中,然后执行SQL。意料之中,SQL执行会出现错误。

此时,数据变为:

image-20240615211516173

我们可以看到,Linda的钱减去了1000,但是Tom的钱并没有增加。

当前来说,每一条SQL语句为一个事务。事务执行完成会自动提交。

查询/设置事务的提交方式

select @@autocommit;  # 查询事务的提交方式
set @@autocommit=0;  # 将事务设置为手动提交

事务提交方式i:

  • @@autocommit为1,是自动提交
  • @@autocommit为0,是手动提交

注意:上诉这种方式,只针对当前会话有效!

改为手动提交事务后,我们要在事务结束时,提交事务,执行COMMIT指令。当事务出现错误时,我们需要手动执行ROLLBACK指令回滚事务。

先将数据恢复为初始数据。

update account set money=2000 where name='Linda' or name='Tom';

将事务提交方式改为手动提交后,我们进行测试一下。

# 1.查询Linda账户余额
select money from account where name='Linda';

# 2.Linda账户余额-1000
update account set money=money-1000 where name='Linda';

# 3.Tom账户+1000
update account set money=money+1000 where name='Tom';

执行如上SQL,当执行完毕后,可以看到表中的数据没有发生改变,这是因为我们将事务提交方式改为了手动,此时的事务还没有提交,我们需要手动执行:

commit;

执行完毕后,可以看到表中的数据发生了改变。

然后,我们再次模拟发生异常的情况。

# 1.查询Linda账户余额
select money from account where name='Linda';

# 2.Linda账户余额-1000
update account set money=money-1000 where name='Linda';

select 模拟异常

# 3.Tom账户+1000
update account set money=money+1000 where name='Tom';

执行如上SQL,当执行完毕后,可以发现报错了。此时,表中的数据也没有发生改变。由于出错了,我们便不能再提交事务,需要回滚事务,所以执行:

rollback;

执行完毕后,可以看到表中的数据和初始值相同,事务中的所有SQL语句均未生效。

开启事务

显式的开启事务,我们不再需要将事务的提交方式改为手动。所以此时,我们执行set @@autocommit=1,将事务提交方式设置为自动。

使用如下指令开启事务:

start transaction
# 或者
begin

同样,我们需要手动提交事务或者回滚事务

commit;
# 或者
rollback;

我们使用这种方式进行测试,首先将事务的提交方式恢复为最初,设置为自动提交。

# 开启事务
start transaction;

# 1.查询Linda账户余额
select money from account where name='Linda';

# 2.Linda账户余额-1000
update account set money=money-1000 where name='Linda';

select 模拟异常

# 3.Tom账户+1000
update account set money=money+1000 where name='Tom';

同时执行上诉SQL语句,会发现报错了。可以看到表中的数据没有发生改变。

此时,上诉的4个操作会被看作为1个事务。此时事务出错,我们应该进行事务的回滚。

rollback;

3. 四大特性ACID

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

4. 并发事务问题

脏读

一个事务读到另一个事务还没有提交的数据。

不可重复读

一个事务先后读取同一条记录,但两次读取的数据不同。

幻读

一个事务按照查询条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在了,好像出现了“幻影”。

4.1 脏读

image-20240616105148340

4.2 不可重复读

image-20240616105159146

4.3 幻读

image-20240616105209646


5. 隔离级别

image-20240616105345058

MySQL事务的默认隔离级别为:Repeatable Read

Oracle事务的默认隔离级别为:Read committed

Serializable隔离级别最高,但是性能最差。

查看事务的隔离级别:

select @@transaction_isolation;  # REPEATABLE-READ

设置事务的隔离级别

set [session|global] transaction isolation level [read uncommitted|read committed|repeatable read|Serializable]
  • session:会话级别
  • global:全局级别

5.1 Read Uncommitted

下面演示脏读问题。

开启两个会话客户端,A客户端和B客户端。

将A客户端,设置事务的隔离级别为Read Uncommitted

set session transaction isolation level read uncommitted;

B客户端的事务隔离级别保持默认,为Repeatable read

此时,数据表中的数据为:

image-20240616111617034

B客户端开启事务,然后执行一条更新语句,不提交事务

start transaction ;
update account set money=money-1000 where name='Linda';

A客户端开启事务,然后查询表中数据

start transaction ;
select * from account;

查询结果为:

image-20240616112210763

此时,B客户端的事务并没有提交,但是A客户端已经读取到了B未提交的事务所更改的数据。这就是脏读问题。

5.2 Read committed

该隔离级别可以解决脏读问题。下面我们进行验证。

我们将客户端A的事务隔离级别设置为Read committed

set session transaction isolation level read committed;

B客户端的事务隔离级别保持默认,为Repeatable read

B客户端开启事务,然后执行一条更新语句,不提交事务

start transaction ;
update account set money=money-1000 where name='Linda';

A客户端开启事务,然后查询表中数据

start transaction ;
select * from account;

查询结果为:

image-20240616112740527

此时,B客户端的事务并没有提交,A客户端并没有读取到了B未提交的事务所更改的数据。

所以,这种隔离级别解决了脏读问题。

该隔离级别下,虽然解决了脏读问题,但依然存在不可重复读问题。

不可重复读问题演示

客户端A的事务隔离级别设置为Read committed。B客户端的事务隔离级别保持默认,为Repeatable read

A客户端执行如下SQL:

start transaction ;
select * from account;

image-20240616143906104

B客户端执行如下SQL:

start transaction ;
update account set money=money-1000 where name='Linda';
commit;

此时A客户端继续执行

select * from account;

image-20240616144015414

此时查询到的结果与之前查询到的结果不一致。在同一个事务中,前后查询结果不一致,这种就是不可重复读。

5.3 Repeatable Read(默认)

该隔离级别下可以解决脏读和不可重复读问题。

下面演示该隔离级别解决不可重复读问题。

我们将客户端A的事务隔离级别设置为Repeatable Read

set session transaction isolation level repeatable read;

B客户端的事务隔离级别保持默认,为Repeatable read

A客户端执行如下SQL:

start transaction ;
select * from account;

image-20240616144406621

B客户端执行如下SQL:

start transaction ;
update account set money=money-1000 where name='Linda';
commit;

此时A客户端继续执行

select * from account;

image-20240616144406621

可以看到,虽然客户端B已经将更新的事务提交了,但是A事务中查询到的数据没有发生改变,保证了前后查询的一致性,解决了不可重复读问题。

此时,客户端A提交事务,执行commit。然后再次进行查询:

select * from account;

image-20240616144615004

可以看到,当客户端A提交事务后,再次查询,查询到的结果为更新后的结果,结果正确!

下面,再测试另外一种情况,当客户端A开启事务,然后客户端B开启事务,客户端B执行了一次更新操作,并提交事务。在客户端B执行更新操作,提交事务后,客户端A再进行查询操作,那么客户端A查询到的结果是更新前的数据,还是更新后的数据呢?

初始数据:

image-20240616144406621

客户端A开启事务:

start transaction ;

客户端B执行如下SQL:

start transaction ;
update account set money=money+1000 where name='Linda';
commit;

客户端A执行如下SQL:

select * from account;

image-20240616145104545

可以看到,客户端A查询到的数据为更新后的数据,因为客户端A在此之前并没有进行该表的数据查询,所以不会保证和之前查询的数据保持一致,所以查询到了更新后的数据。

此时,如果客户端B再执行如下SQL:

start transaction ;
update account set money=money+1000 where name='Linda';
commit;

客户端A执行如下SQL:

select * from account;

那么此时,查询结果如何呢?

image-20240616145104545

可以看到,查询结果为更新前的结果,没有发生改变,解决了不可重复读问题。

image-20240616150100642

该隔离级别解决了脏读和不可重复读问题,但是依然存在幻读问题。

幻读问题演示

客户端A的事务隔离级别设置为Repeatable read。B客户端的事务隔离级别保持默认,为Repeatable read

客户端A执行:

start transaction ;
select * from account;

image-20240616150639595

客户端B执行如下SQL:

start transaction ;
insert into account values(5,'James',1000);
commit;

由于客户端A一开始没有查询到James这条数据,此时客户端A继续执行:

insert into account values(5,'James',1000);

image-20240616150841197

会发现,出错了!该主键已经存在了,但是客户端A刚刚查询到结果中是没有该记录的!

此时客户端A再次执行查询命令:

select * from account;

由于该隔离级别下解决了不可重复读问题,结果会和之前的查询结果一致,所以查询结果中仍然没有James这条记录。这就是幻读问题。

5.4 Serializable

该隔离级别下可以解决脏读、不可重复读和幻读问题。

下面演示该隔离级别解决幻读问题。

我们将客户端A的事务隔离级别设置为Serializable

set session transaction isolation level Serializable;

B客户端的事务隔离级别保持默认,为Repeatable read

客户端A执行:

start transaction ;
select * from account;

image-20240616151327049

客户端B执行如下SQL:

start transaction ;
insert into account values(6,'Bob',2000);
commit;

此时,会发现,SQL并没有执行完成。

image-20240616151521682

SQL执行卡在了插入数据这个位置。它在等待客户端A事务的结束。客户端A事务相当于对该表加了个锁。

如果此时,客户端A不再执行任何操作,直接提交事务,执行commit,那么客户端B这段SQL则会执行下去,执行成功。数据成功被插入。

如果此时,客户端A执行了如下操作:

insert into account values(6,'Bob',2000);
commit;

那么客户端B则会直接失败。因为主键6已经存在。

如果客户端A一开始只是开启了事务,但是没有对account表进行查询操作,那么此时客户端B可以正常开启事务,向account表中插入数据,提交事务。

当客户端A开启了事务且对account表进行了一次操作后,客户端B就需要等待客户端A对事务结束后,才可以继续对account表进行操作。但是查询操作是可以正常进行。

事务的隔离级别越高,但是性能越低!

一般会使用数据库的默认隔离级别。

MySQL 事务是指一组数据库操作,这些操作要么全部执行,要么全部不执行,其目的是保证在并发环境下,数据的一致性和完整性。MySQL 事务具有 ACID 性质,即原子性、一致性、隔离性和持久性。 MySQL 中使用事务需要使用 BEGIN、COMMIT 和 ROLLBACK 语句,其中 BEGIN 表示开启一个事务,COMMIT 表示提交事务,ROLLBACK 表示回滚事务事务的基本语法如下: ``` BEGIN; -- 执行一组数据库操作 COMMIT; -- 提交事务 -- 或者 ROLLBACK; -- 回滚事务 ``` 在 MySQL 中,事务的隔离级别分为四个等级,分别是 Read Uncommitted、Read Committed、Repeatable Read 和 Serializable。隔离级别越高,数据的一致性和完整性越高,但同时也会影响数据库的性能。 MySQL 事务的 ACID 性质有以下含义: 1. 原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部失败回滚,不会只执行其中的一部分操作。 2. 一致性(Consistency):事务执行前后,数据库中的数据必须保持一致性状态,即满足数据库的约束条件和完整性规则。 3. 隔离性(Isolation):事务之间应该是相互隔离的,一个事务的执行不应该被其他事务干扰,保证事务之间的数据相互独立。 4. 持久性(Durability):事务提交后,对数据库的修改应该是永久性的,即使出现系统故障或电源故障,也不应该对数据产生影响。 总之,MySQL 事务是一组数据库操作,具有 ACID 性质,可以通过 BEGIN、COMMIT 和 ROLLBACK 语句来实现,隔离级别越高,数据的一致性和完整性越高,但同时也会影响数据库的性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mango1698

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值