MySQL基础——事务
上篇文章学习了MySQL基础——多表查询,这篇文章学习MySQL基础——事务。
事务
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,所以这些操作要么同时成功,要么同时失败。
事务的四大特性:
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败;
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态;
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行;
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变是永久的。
MySQL的事务默认是自动提交的,当执行一条DML语句,MySQL会隐式的提交事务。
默认自动提交事务可能会导致数据出错,例如,有如下数据表:
接下来我们模拟收付款,同时执行如下代码:
update account set money=money - 1000 where name='买家'; # 买家付款
update account set dasa money=money + 1000 where name='商家'; # 商家收款
执行结果如下图所示:
由于第二条MySQL语句是错误的,MySQL自动提交事务错误,导致商家没有收到款。
事务操作
既然自动提交事务可能会导致数据错误,那么我们把自动提交事务改为手动提交事务。
手动提交事务
在修改事务提交方式之前,执行如下代码查看事务的提交方式:
select @@autocommit;
如下图所示:
其中1为自动提交事务,0为手动提交事务。
查看事务提交方式后,执行如下代码把事务提交方式改为手动提交:
set @@autocommit=0;
如下图所示:
把事务提交方式改为手动提交后,需要执行如下代码提交事务和回滚事务:
commit; #提交事务
rollback; #回滚事务
接下来我们还是模拟收付款来演示手动提交事务,同时执行如下代码:
update account set money=money - 1000 where name='买家'; # 买家付款
update account set money=money + 1000 where name='商家'; # 商家收款
发现数据没有发生改变,如下图所示:
这是因为还没提交事务,此时我们只需要执行如下代码数据会发生改变:
commit;
如下图所示:
接下来我们同时执行如下代码演示事务执行出错的情况:
update account set money=money - 1000 where name='买家';
update account set dasa money=money + 1000 where name='商家';
rollback; # 回滚事务
如下图所示:
通过回滚事务把正确和错误的事务修改值恢复到修改前的值。
自动提交事务中手动提交
当我们不想把自动提交事务改为手动提交事务,而防止自动提交事务出错时,可以在自动提交事务中,手动开始事务,代码如下:
start transaction 或 begin;
示例代码如下:
set @@autocommit=1; # 把事务提交方式改为自动提交
start transaction; # 开始事务
update account set money=money - 1000 where name='买家'; # 买家付款
update account set money=money + 1000 where name='商家'; # 商家收款
如下图所示:
发现数据没有发生改变,这时只需要执行如下代码数据就会发生改变:
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;
如下图所示:
接下来通过两个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; # 查看数据
如下图所示:
在窗口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;
如下图所示:
这样就成功解决了脏读问题。虽然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; # 查看数据
如下图所示:
在窗口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; # 查看数据
如下图所示:
在窗口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';
如下图所示:
在窗口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);
如下图所示:
在窗口1查询id为5的数据,因为没有id为5的数据,所以没有查询到,在窗口2中插入id为5的数据,发现一直没有插入成功,这是因为在窗口1中已经开启了一个事务,需要窗口1开启的事务结束后,才能执行窗口2的事务,这样就解决了幻读问题。
好了,MySQL基础——事务就学到这里了,下篇文章学习MySQL进阶——存储引擎。
公众号:Python侦探