MySQL进阶—事务

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL进阶—事务相关的知识,希望对你有一定的参考价值。

事务

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

事务的四大特性:

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

mysql的事务默认是自动提交的,当执行一条DML语句,MySQL会隐式的提交事务。

默认自动提交事务可能会导致数据出错,例如,有如下数据表:


MySQL进阶—事务_数据


接下来我们模拟收付款,同时执行如下代码:


update account set money=money - 1000 where name=买家;          # 买家付款
update account set dasa money=money + 1000 where name=商家; # 商家收款

执行结果如下图所示:


MySQL进阶—事务_数据_02


由于第二条MySQL语句是错误的,MySQL自动提交事务错误,导致商家没有收到款。

事务操作

既然自动提交事务可能会导致数据错误,那么我们把自动提交事务改为手动提交事务。

手动提交事务

在修改事务提交方式之前,执行如下代码查看事务的提交方式:


select @@autocommit;

如下图所示:


MySQL进阶—事务_数据库_03


其中1为自动提交事务,0为手动提交事务。

查看事务提交方式后,执行如下代码把事务提交方式改为手动提交:


set @@autocommit=0;

如下图所示:


MySQL进阶—事务_数据_04


把事务提交方式改为手动提交后,需要执行如下代码提交事务和回滚事务:


commit;         #提交事务
rollback; #回滚事务

接下来我们还是模拟收付款来演示手动提交事务,同时执行如下代码:


update account set money=money - 1000 where name=买家;          # 买家付款
update account set money=money + 1000 where name=商家; # 商家收款

发现数据没有发生改变,如下图所示:


MySQL进阶—事务_提交事务_05


这是因为还没提交事务,此时我们只需要执行如下代码数据会发生改变:


commit;

如下图所示:


MySQL进阶—事务_数据_06


接下来我们同时执行如下代码演示事务执行出错的情况:


update account set money=money - 1000 where name=买家;          
update account set dasa money=money + 1000 where name=商家;
rollback; # 回滚事务

如下图所示:


MySQL进阶—事务_数据库_07


MySQL进阶—事务_数据_08


通过回滚事务把正确和错误的事务修改值恢复到修改前的值。

自动提交事务中手动提交

当我们不想把自动提交事务改为手动提交事务,而防止自动提交事务出错时,可以在自动提交事务中,手动开始事务,代码如下:


start transaction  begin;

示例代码如下:


set @@autocommit=1;         # 把事务提交方式改为自动提交
start transaction; # 开始事务
update account set money=money - 1000 where name=买家; # 买家付款
update account set money=money + 1000 where name=商家; # 商家收款

如下图所示:


MySQL进阶—事务_提交事务_09


MySQL进阶—事务_提交事务_10


发现数据没有发生改变,这时只需要执行如下代码数据就会发生改变:


commit;

并发事务问题

当有两个或两个以上的事务同时执行、提交事务时,可能会引发并发事务问题,并发事务常见的问题有:

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

事务隔离级别

为了解决事务并发问题,我们设置事务的隔离级别,不同的隔离级别可以解决不同的事务并发问题,如下表:

隔离级别

脏读

不可重复读

幻读

Read uncommitted

不可解决

不可解决

不可解决

Read committed

可解决

不可解决

不可解决

Repeatable Read(默认)

可解决

可解决

不可解决

Serializable

可解决

可解决

可解决

在设置事务的隔离级别之前,我们首先执行如下代码看看事务的隔离级别:


select @@transaction_isolation;

设置事务隔离级别语法格式如下:


set [session | global] transaction isolation level read uncommitted | read committed | repeatable read | serializable

其中session是当前会话窗口有效,global是对当前客户端的所有会话窗口有效。

接下来我们来演示隔离级别解决事务并发问题。

Read uncommitted

首先依次执行如下代码查看和设置隔离级别:


select @@transaction_isolation;
set session transaction isolation level read uncommitted;

如下图所示:


MySQL进阶—事务_数据库_11


接下来通过两个cmd窗口通过start transaction手动开启两个事务,并依次执行如下代码:


# 窗口1
use mytest; # 选择mytest数据库
set session transaction isolation level read uncommitted; # 设置事务级别为read uncommitted
start transaction; # 开始事务
select * from account; # 查看数据

#窗口2
use mytest; # 选择mytest数据库
start transaction; # 开始事务
update account set money=money - 1000 where name=买家; #更新数据

# 窗口1
select * from account; # 查看数据

如下图所示:


MySQL进阶—事务_数据库_12


在窗口2更新数据事务后,没有提交事务,但在窗口1查看数据发现数据已经更新了,这就属于脏读了。

Read committed

接下来我们通过设置Read committed事务隔离级别解决脏读问题,同样打开两个cmd窗口通过start transaction手动开启两个事务,并依次执行如下代码:


# 窗口1
use mytest; # 选择mytest数据库
set session transaction isolation level read committed; # 设置事务级别为read committed
start transaction; # 开始事务
select * from account; # 查看数据

#窗口2
use mytest; # 选择mytest数据库
start transaction; # 开始事务
update account set money=money - 1000 where name=买家; #更新数据

# 窗口1
select * from account; # 查看数据

#窗口2
commit;

如下图所示:


MySQL进阶—事务_数据_13


这样就成功解决了脏读问题。虽然Read committed隔离级别可以解决脏读问题,解决不了不可重复读和幻读。

接下来演示不可重复读,示例代码如下:


# 窗口1
use mytest; # 选择mytest数据库
set session transaction isolation level read committed; # 设置事务级别为read committed
start transaction; # 开始事务
select * from account; # 查看数据

#窗口2
use mytest; # 选择mytest数据库
start transaction; # 开始事务
update account set money=money - 1000 where name=买家; #更新数据

# 窗口1
select * from account; # 查看数据

#窗口2
commit; # 提交事务

#窗口1
select * from account; # 查看数据

如下图所示:


MySQL进阶—事务_数据_14


在窗口1中一次事务执行了3次查询数据表操作,前面两次结果相同,第三次查询的结果不同,这就叫不可重复读。

Repeatable Read

为了解决不可重复读,把事务隔离级别改为Repeatable Read,示例代码如下 :


# 窗口1
use mytest; # 选择mytest数据库
set session transaction isolation level repeatable read; # 设置事务级别为repeatable read
start transaction; # 开始事务
select * from account; # 查看数据

#窗口2
use mytest; # 选择mytest数据库
start transaction; # 开始事务
update account set money=money + 1000 where name=买家; #更新数据
commit;

# 窗口1
select * from account; # 查看数据

# 窗口1
commit; # 提交事务
select * from account; # 查看数据

如下图所示:


MySQL进阶—事务_数据_15


在窗口1开启一个事务并查询数据表的数据后,在窗口2中开启事务并更新提交事务,再返回窗口1中查询数据表发现查询结果和之前查询一样,这就叫可重复读,在窗口1中提交第一个事务后,再次查询数据表数据发现数据发生了改变。这就解决了并发问题的不可重复读。

Repeatable Read可以解决脏读和不可重复读,但解决不了幻读。

接下来我们演示幻读,示例代码如下:


# 窗口1
use mytest;
start transaction;
select * from account where id=3;

# 窗口2
use mytest;
start transaction;
insert into account(id,name,money) values(3,中间商,2000);
commit;

# 窗口1
insert into account(id,name,money) values(3,大中间商,2000);
select * from account where id=3;

如下图所示:


MySQL进阶—事务_数据_16


在窗口1中开启事务并查询id为3的数据,发现数据表中没有id为3的数据,在窗口2中,开启事务并添加id为3的数据并提交事务后,在窗口1中添加id为3的数据,发现已经有id为3的数据了,但查询id为3的数据时,发现没有查到,这就是幻读。

Serializable

为了解决幻读并发事务问题,我们把事务隔离级别改为Serializable,示例代码如下:


# 窗口1
use mytest; # 选择mytest数据库
set session transaction isolation level Serializable; # 设置事务级别为Serializable
start transaction; # 开始事务
select * from account where id=5; # 查看数据

# 窗口2
use mytest;
start transaction;
insert into account(id,name,money) values(5,厂商,2000);

如下图所示:


MySQL进阶—事务_数据_17


在窗口1查询id为5的数据,因为没有id为5的数据,所以没有查询到,在窗口2中插入id为5的数据,发现一直没有插入成功,这是因为在窗口1中已经开启了一个事务,需要窗口1开启的事务结束后,才能执行窗口2的事务,这样就解决了幻读问题。

以上是关于MySQL进阶—事务的主要内容,如果未能解决你的问题,请参考以下文章

礼品代发平台:ab单,拍a发b操作5步法

高省一个了解网络商家和买家心里的APP,它知道你想要什么

MYSQL进阶学习笔记九:MySQL事务的应用!(视频序号:进阶_21-22)

MySQL进阶—事务

MySQL进阶MySQL事务详解

MySQL进阶(TCL 事务控制语言)