前言
对于事务的隔离级别及innodb的对各种隔离级别的实现方式一直以来都不是完全理解,故整理了本片文章希望能和大家一起学习,探讨~~
一、隔离级别
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | v | v | v |
已提交读(Read committed) | x | v | v |
可重复读(Repeatable read) | x | x | v |
可串行化(Serializable ) | x | x | x |
不通的隔离级别造成不同的影响,主要原因是由一个事务的的读取在另外一个事务不同的生命周期导致的,例如:
- 未提交读(Read uncommitted),发生了脏读的情况
时间点 | 事务A | 事务B |
---|---|---|
1 | 开启事务A | |
2 | 开启事务B | |
3 | 查询库存为100 | |
4 | 库存增加至150 | |
5 | 查询库存为150 |
- 已提交读(Read committed),发生了不可重复读的情况
时间点 | 事务A | 事务B |
---|---|---|
1 | 开启事务A | |
2 | 开启事务B | |
3 | 查询库存为100 | |
4 | 库存增加至150 | |
5 | 查询库存为100 | |
6 | 提交事务 | |
7 | 查询库存为150 |
- 可重复读(Repeatable read)
时间点 | 事务A | 事务B |
---|---|---|
1 | 开启事务A | |
2 | 开启事务B | |
3 | 查询库存为100 | |
4 | 库存增加至150(阻塞) | |
5 | 查询库存为100 | |
6 | 继续阻塞 | |
7 | 提交事务 | |
8 | 更新库存 |
注意:这里只是innodb的rr模式下当前读的处理机制
可以看出rr模式下,无论事务是否提交,读的结果都是相同的,这就可以实现可重复读。但是rr模式下,会出现幻读(Phantom Read)现象:
时间点 | 事务A | 事务B |
---|---|---|
1 | 开启事务A | |
2 | 开启事务B | |
3 | 查询id<3的所有记录,共3条 | |
4 | 插入一条记录id=2 | |
5 | 提交事务 | |
6 | 查询id<3的所有记录,共4条 |
rr模式下,除了会出现幻读之外,实际业务使用中还会出现丢失更新的问题:
时间点 | 事务A | 事务B |
---|---|---|
1 | 开启事务A | |
2 | 开启事务B | |
3 | 查询库存s为100 | |
4 | 更新库存为s为150 | |
5 | 增加20库存,即更新库存位100+20=120 | |
6 | 提交事务 |
结果是库存更新丢了b事务的更新,解决这种问题通常两种方法
1.使用乐观锁
2.update table set s = s + 20;
到此基本上就是理论上各隔离级别下可能出现的问题,下面讨论innodb是如何避免以上各类问题的。
二、当前读和快照读
1、当前读:即加锁读,读取记录的最新版本,会加锁保证其他并发事务不能修改当前记录,直至获取锁的事务释放锁;
使用当前读的操作主要包括:显式加锁的读操作与插入/更新/删除等写操作,如下所示:
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
2、快照读:即不加锁读,读取记录的快照版本而非最新版本,通过MVCC实现;
InnoDB默认的RR事务隔离级别下,不显式加『lock in share mode』与『for update』的『select』操作都属于快照读,保证事务执行过程中只有第一次读之前提交的修改和自己的修改可见,其他的均不可见;
三、当前读innodb机制
第一部分中讨论的各种情况都是针对当前读的,innodb的rr隔离级别在当前读下是通过加两段加锁避免了不可重复读和幻读现象,下面说一下innodb的锁。
- innodb的行锁类型:S(共享)锁、X(排他)锁
锁类型 | S | X |
---|---|---|
S | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 |
innodb的锁算法
- Record Lock:单行锁
- Gap Lock:间隙锁
- Next-Key:Record Lock + Gap Lock
具体锁算法可参考子梁兄的
讲解
这里为了讲解锁算法我们创建测试表t:
CREATE TABLE `t` (
`id` bigint,
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO t SELECT 1;
INSERT INTO t SELECT 2;
INSERT INTO t SELECT 5;
表里共有三条数据,我们执行如下操作:
时间点 | 事务A | 事务B | |
---|---|---|---|
1 | begin; | ||
2 | begin; | ||
3 | select * from t where a = 5 for update; | ||
4 | insert into t (id) values (4); | ||
5 | commit(成功) |
这就实现了基本锁算法降级,从Next-Key降级到Record Lock算法,类似具体锁算法中有很多类似的降级目的就是为了在保证一致性的前提下提高并发量。而innodb就是通过给查询范围内的所有数据加X锁来阻塞其他事务的插入更新操作,从而避免了不可重复读和幻读。虽然这样避免了不可重复读和幻读,但是大大的降低了数据库的并发性能。
一致性非锁定读(consistent nonlocking read)极大的提高了数据库的并发性,也就是我们通常多的快照读,在innodb中通过MVCC实现快照读。
四、MVCC
MVCC的最大好处:读不加任何锁,读写不冲突,对于读操作多于写操作的应用,极大的增加了系统的并发性能;
InnoDB默认的RR事务隔离级别下,不显式加『lock in share mode』与『for update』的『select』操作都属于快照读,使用MVCC,保证事务执行过程中只有第一次读之前提交的修改和自己的修改可见,其他的均不可见。
mvcc避免幻读
未完待续