事务机制
事务的四个隔离级别
- 读取未提交的数据
- 读取已提交的数据
- 重复读取
- 序列化
事务选择哪个隔离级别是根据具体的业务场景来使用的,并没有哪个绝对的好坏
读取未提交的数据
比如下面的案例:
- 前提:买票系统中的某个座位还未售出:D座
- A事务读取到D座未售出状态,然后将他改成已售出,但是此时这个事务还没有提交,只是A事务的redo日志中显示已售出
- 此时B事务看到D座未售出状态,然后将他改成已售出并且在A之前提交了事务;
- 那么A事务在提交的时候因为此时的座位状态已经被改变,因此A事务就触发了回滚,这种情况对于用户来说,体验上不好
- 那么此时就需要在A事务改变数据但是还未提交事务前,B事务可以读取A事务的临时文件,也就是A事务的redo日志,当B事务发现这个状态是已售出就不会购买D座,它可以去购买E座,这样就不会引发事务的冲突了
- 这种情况可以使用的隔离级别就是:读取未提交数据
修改事务隔离级别:以读取其他事务未提交的数据
- read uncommitted
- 会话:Navicat中打开一个查询面板,这个相当于一次会话,关闭这个查询面板就是关闭了这次会话
- 用法:设置当前会话的隔离级别,隔离级别只是针对当前会话的,不是全局的
set session transaction isolation level read uncommitted
栗子:
打开Navicat,有下面的表:
score:
打开3个查询面板
面板1,A事务修改表中grade为100,但是不提交事务
start transaction;
update score set grade=100;
select * from score;
此时事务内读取到的是修改后的数据
面板2,B事务,不设置隔离级别,直接读取表数据:
start transaction;
select * from score;
这里默认读取的是已提交的数据,这里看到数据并未修改,因为A事务还没有提交
面板3,C事务,设置隔离级别:读取其他事务未提交的数据
set session transaction isolation level read uncommitted;
start transaction;
select * from score;
根据结果可以看到读取的是A事务还未提交的数据
读取已提交数据
比如下面的案例:
- 原本的账户中有1000元
- 若是A事务给账号添加1000元,此时redo日志中账户金额是2000,
- B事务是取出100元,且在A事务未提交前读取未提交的数据,那此时就是1900
- 若是A事务因为一些原因最终回滚了,然后B事务又提交了,那此时的1900元的数据就是有问题的
- 因此,这种银行业务,读取用户当前账号中的金额绝对不能读取未提交的数据,而是读取已提交的数据
修改事务隔离级别为:只能读取其他事务提交的数据
- read committed
具体用法:
set session transaction isolation level read committed
栗子:
接着前面的栗子,A事务还未修改,此时打开一个新查询面板,创建C事务,设置读取已提交的数据
set session transaction isolation level read committed;
start transaction;
select * from score;
根据结果,可以看到读取的是已提交的数据,A事务还未提交,修改的数据还未同步:
重复读取
- 如果淘宝上一个商品,用户在下单后(还未支付前),商品的价格发生了变化,那么这种情况一般的是更改后的商品价格不会影响更改前已下单的商品价格
- 比如说A事务是下单商品,但是这个事务还未提交,此时B事务给商品涨价了,那么不论B事务是否提交,A事务数据的读始终都是下单时的价格(即A事务undo日志中的数据)
- 也就是说A事务一旦开启,其他事务进行的操作不会影响到A事务的数据
修改事务的隔离级别:事务在执行中反复读取数据,得到的结果是一致的,不会受其他事务影响
- repeatable read
具体用法:
set session transaction isolation level repeatable read
栗子:
Navicat中2个面板:
面板1,A事务,读取表中数据:
set session transaction isolation level repeatable read;
start transaction;
select * from score;
面板2,B事务,修改表中数据并提交事务:
start transaction;
update score set grade=100;
select * from score;
commit;
此时表中数据被修改了:
面板1,删除前2条数据,保留一条查询,查看表数据
select * from score;
表中仍然是A事务开始时的数据
事务的序列化
- 由于事务并发执行所带来的各种问题,前三种隔离级别只使用在某些业务场景中,但是序列化的隔离性,让事务逐一执行,就不会产生上述问题了
- 这种情况下事务不能并发执行,使用的比较少
set session transaction isolation level serializable
栗子:
Navicat中2个面板
面板1,A事务,会话隔离性设置成序列化
set session transaction isolation level serializable;
面板2,B事务,开启事务后不提交事务:
start transaction;
update score set grade=200;
select * from score;
面板1,查询语句,执行后发现没有出现查询结果,这是因为B事务还未提交,A事务不能执行:
select * from score;
面板2,提交事务后,此时面板1中的查询结果出现了
commit;