事务
目录
一、事务简介
- 事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作
要么同时成功,要么同时失败
。
正常情况下银行中张三给李四转账的案例:
异常情况下:
此时我们的转账失败了,但是白白丢失了1k!但是用事务控制的话,这三个操作为一个集合,要成功都成功,要失败都失败!就可以解决这种“钱丢了”的异常。
二、事务操作
2.1数据演示
- 数据准备
create table account(
id int auto_increment primary key comment '主键ID',
name varchar(50) comment '姓名',
money int comment '余额'
)comment '账户表';
insert into account(id,name,money) values (null,'张三',2000),(null,'李四',2000);
- 执行正常转账操作
#转账操作
# 1、查询张三余额
select money from account where name = '张三';
# 2、将张三账户的余额-1000
update account set money = money-1000 where name = '张三';
# 3、将李四账户的余额+1000
update account set money = money+1000 where name = '李四';
转账成功,没有任何问题。
- 下面我们模拟一下出现异常问题的情况:
我们在程序段执行中间加入了一段没有带注释的文字,也就是会使程序出现报错异常!
这个时候程序报错,但是我们查询余额后发现
张三的钱扣了!但是李四的钱缺没有加!也就是说我们丢失了1k!
2.2事务控制
①方式一:
- 设置/查看事务提交方式
select @@autocommit;#查看事务的提交方式,如果为1,就是自动提交,如果为0,就是手动提交
set @@autocommit = 0;#通过set指令设置系统变量,将事务的提交方式改为手动提交
- 提交事务
commit;
- 回滚事务
rollback;
例如:我现在设置事务为手动提交,然后再次执行张三给李四转账的事务。
set @@autocommit = 0;#通过set指令设置系统变量,将事务的提交方式改为手动提交
#转账操作
# 1、查询张三余额
select money from account where name = '张三';
# 2、将张三账户的余额-1000
update account set money = money-1000 where name = '张三';
# 3、将李四账户的余额+1000
update account set money = money+1000 where name = '李四';
在执行后,我们发现,张三和李四的账户都没有改变。
这是因为我们现在开始了手动提交事务,所以需要我们手动commit之后才可以提交事务!
commit;
如果我们在执行事务的操作过程中出现了异常报错,这时我们直接用回滚事务即可
rollback;
②方式二:
首先我们需要打开事务的自动提交,即set @@autocommit = 1;
- 开启事务
start transaction 或 begin;
- 提交事务
commit;
- 回滚事务
rollback;
#方式二:
start transaction ;
# 1、查询张三余额
select money from account where name = '张三';
# 2、将张三账户的余额-1000
update account set money = money-1000 where name = '张三';
# 3、将李四账户的余额+1000
update account set money = money+1000 where name = '李四';
#如果所有的事务执行成功,则提交事务
commit ;
#否则回滚
rollback ;
三、事务四大特性
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
四、并发事务问题
MySQL支持多事务并发执行
并发执行的意思就是“可以在操作系统上同时运行多个事务进程”
那么既然事务可以并发执行,这里就出现了一些问题:一个事务在写数据时,另一个事务要读这行数据,该怎么处理?一个事务在写数据,另一个数据也要写这行数据,又该如何处理冲突?
这就是并发事务所产生的一些问题,具体来说就是:脏读、不可重复读和幻读
4.1脏读
脏读指的是读到了其他事务未提交的数据
,未提交的数据意味着这些数据可能回滚,也就是最终可能并不存在数据库中。读到了最终并不一定存在的数据,这就是脏读。
脏读最大的问题就是可能会读到不存在的数据。比如在上图中,事务B的更新数据被事务A读取,但是事务B回滚了,更新数据全部还原,也就是事务A刚刚读到的数据并没有存在于数据库中。
从宏观来看,就是事务A读出了一条并不存在于数据库中的数据,这个问题是很严重的。
4.2不可重复读
不可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。
事务A多次读取同一数据,但事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
1和3select查询到的数据不一样!
4.3幻读
幻读,并不是说两次查询获得的结果集不同,幻读侧重的方面是某一次的select操作得到的结果所表征的数据状态无法支撑后续的业务操作。
更为具体一点就是:select查询某条记录,发现该记录不存在,准备插入此记录,但执行insert时发现此记录已存在,无法插入,此时就发生了幻读。
比如这两个事务的执行过程,事务A的第一步,select查询id为1的数据,发现DB数据库中没有id为1的数据,此时并发执行事务B执行了insert插入id为1的数据;同时,事务A的并发操作2并没有察觉到事务B执行了insert操作,所以事务A的2执行了insert插入id为1的数据,但是因为id是主键,所以此时发现操作失败,有一种“幻觉”的味道,所以叫幻读。
如果在我们已经解决不可重复读问题的前提下(也就是通过设置隔离级别,事务A读不到其它事务已经提交的内容
)
此时事务A的操作3再次执行select查询id为1的数据,发现仍然查不到
!!!但是刚刚的insert操作明明已经确定了当前数据库中有数据了,insert执行不了,但是确又查不到!
这就是真正的“幻读”!
五、事务隔离级别
- 查看事务的隔离级别
select @@transaction_isolation;
- 设置事务隔离级别
set [session|global] transaction isolation level {四种隔离级别};
例如:设置当前会话的事务隔离级别为Read committed
set session transaction isolation level read committed ;
- 模拟不同的事务隔离级别
因为模拟并发事务执行需要两个以上客户端数据库同时进行操作,所以我们在cmd命令行打开两个客户端,连接到同一个数据库。
演示Read uncommitted读未提交级别下的脏读问题
1、首先我们需要设置隔离级别,开启两个client端的事务。
2、我们让B事务执行update操作,但并不提交;A事务此时再执行select操作,可以查到没有提交的数据状态,此时产生脏读。
演示Read committed读已提交级别下的解决脏读问题
1、我们通过修改事务的隔离级别为Read committed,这时我们再次在B事务执行update操作不提交,然后在A事务执行select操作,发现并没有产生脏读问题。
2、虽然解决了脏读问题,但是还会产生不可重复读问题。
因为我们解决了脏读问题,所以在B事务commit前,我们并不会读到执行过update操作之后的数据状态,但是我们在B事务commit之后,再次执行读操作,就能读到update之后的数据状态了。这和我们之前读到的数据不一样,所以叫做不可重复读!
演示Repeatable Read级别下解决不可重复读问题
1、在我们将事务的隔离级别设置为Repeatable Read之后,再次尝试之前出现的不可重复读操作,也就是B事务执行update操作,并commit提交,A事务在commit之前的两次查询结果不一致,但是这时候我们就能做到一致了!
2、演示该事务隔离级别下的幻读
演示Serializable级别下解决幻读问题
该级别其实就是规定了事务进程只能执行单表操作,也就是说,如果我先开启了A事务,然后开启B事务,想要在B事务执行insert操作,此时insert操作并不能执行,而是会一直在缓冲区等待A事务commit执行完毕。
只有在A事务commit执行完毕后,B事务的insert操作才会执行,即串行化
操作。
和操作系统中的PV控制进程同步操作很像!