断断续续花了些时间,参考挺多文章,在自己的理解上总结出来以下内容,
以后忘了的时候可以翻出来看看。如果大家之前不太了解这一部分的东西,
可花点耐心把这篇看完,我想会对各位有一些帮助的,若无收获你来砍我。
InnoDB默认隔离级别-可重复读是如何实现的
- mysql多事务并发的场景下会出现的问题:
- 1、脏读
- 2、不可重复读
- 3、幻读
- 隔离级别
- 1、读未提交(Read Uncommitted)
- 2、读已提交(Read Committed)
- 3、可重复读(Repeatable Read)
- 4、可串行化(Serializable)
- 常见锁
- 乐观锁和悲观锁
- 共享锁(Shared Lock)也叫读锁或S锁
- 排它锁(Exclusive Lock)也叫写锁或X锁
- 行锁(Record Lock)
- 间隙锁(Gap Lock)
- Next-Key Lock
- MVCC(multiversion concurrency control)
- 事务版本号
- MVCC在InnoDB中的实现
- 在RR可重复读隔离级别下的增删查改
- delete:
- update:
- insert:
- select:
- 快照读(snapshot read)和当前读(locking read)
- 快照:
- 快照读:
- 当前读:
- InnoDB如何在可重复读级别解决了幻读的问题
- 可重复读
- 间隙锁
mysql多事务并发的场景下会出现的问题:
1、脏读
事务A执行过程中的一次查询,读到了事务B修改但并未提交的数据,
后来B回滚,导致A读到了错误数据,这是脏读。
2、不可重复读
事务A两次相同查询的间隔中,事务B修改了这些记录的数据并提交,
导致A两次读到的记录数据不一致,这是不可重复读。
3、幻读
事务A两次相同查询的间隔中,事务B插入了符合条件的记录并提交,
导致A两次读到的记录条数不一致,这是幻读。
隔离级别
标准隔离级别总共有四种
1、读未提交(Read Uncommitted)
一般不会用于实际项目。
2、读已提交(Read Committed)
大部分数据库的默认隔离级别,如oracle、sql server等,
但是不包括mysql InnoDB。
3、可重复读(Repeatable Read)
mysql InnoDB默认的隔离级别。
4、可串行化(Serializable)
事务隔离的最高级别,会使数据库的并发性能大幅下降,现实中应用很少。
各隔离级别对应的问题:
常见锁
在分析今天的问题之前,我们先了解一些常见锁的基础概念。
乐观锁和悲观锁
它们不是真实存在的锁,只是一种机制或者说是理论。
共享锁(Shared Lock)也叫读锁或S锁
若事务A对数据对象a加上S锁,则事务A只能读a;
其他事务还可以再对a加S锁,却不能加X锁,直到A释放a上的S锁;
这样保证了其他事务可以读a,但在A释放a上的S锁之前不能对a做任何修改。
排它锁(Exclusive Lock)也叫写锁或X锁
若事务A对数据对象a加上X锁,则事务A可以读或写a,其他事务则不可以再对a加任何锁。
行锁(Record Lock)
锁住对应的行,使其它事务无法修改这些数据,但是无法限制insert操作。
间隙锁(Gap Lock)
锁住一个范围,使其它事务无法在该范围内插入数据。(这个我后面会再细说)
Next-Key Lock
Record Lock + Gap Lock。
MVCC(multiversion concurrency control)
多版本并发控制MVCC是以乐观锁为理论基础的一种可以用来增强并发性的强大技术。
事务版本号
事务版本号是一个从1开始的自增计数,是事务的唯一标识,
事务开启的时候分配,版本号的大小可以用来表示事务的串行化顺序,用于事务可见性的判断。
MVCC在InnoDB中的实现
在InnoDB中,会在每行数据后添加两个额外的隐藏的值,
这两个值一个记录这行数据何时被创建(创建它的事务的版本号),
另外一个记录这行数据何时被删除(删除它的事务的版本号)。
在RR可重复读隔离级别下的增删查改
delete:
记录对应行的删除版本号为当前事务版本号,delete也是假删除。
update:
1、删除原来记录,删除版本号同上;
2、新增一条记录,创建版本号为当前事务版本号。
insert:
同上第2步。
select:
查询创建版本号<=当前事务版本号and(删除版本号>当前事务版本号 or 删除版本号为null)的记录。
快照读(snapshot read)和当前读(locking read)
快照:
事务中第一个这样读读到的数据的一个readView,即该时间点数据的一个快照。
快照读:
在RR隔离级别下,同一事务中后边所有这样的读读到的都是第一个这样读的快照,
或是被当前事务刷新过的快照,这样做到了可重复读。
当前读:
读取当前最新版本的数据。
(当前读的具体定义,大家可以去打听一下,这里只是顺便提一下。)
举个简单的例子:
假设事务A更新表中某一记录的a字段,而事务B也更新这条记录的b字段,
事务B先提交,如果事务A读取的是这条记录快照版本,那么它看不到事务B所提交的修改,
在此基础上更新的话就会覆盖B之前的修改,这样一来,B的修改就丢失了,而这样是不可接受的。
所以,在写的时候一定不是快照读,而是当前读。
InnoDB如何在可重复读级别解决了幻读的问题
可重复读
这段话的大致意思是,在默认的隔离级别中,普通的SELECT用的是快照读不加锁。
而对于当前读、UPDATE和DELETE,则需要加锁,至于加什么锁视情况而定:
如果对一个唯一索引使用了唯一的检索条件,那么只需锁定索引记录即可;
如果你没有使用唯一索引作为检索条件,或者用到了索引范围扫描,
那么将会使用间隙锁或者next-key锁以此来阻塞其它会话向这个范围内的间隙INSERT数据,避免幻读;
如果你使用没有索引的字段作为检索条件,那么会给全表加上X锁!
间隙锁
下面是InnoDB的聚集索引结构图,其中非叶子节点里蓝色的区域即是索引之间的间隙。
假设一个索引包含以下几个值:10、11、13、20,那么表的索引间隙:
(负无穷, 10),
(10, 11),
(11, 13),
(13, 20),
(20, 正无穷)。
至于InnoDB为什么要采用间隙锁这样的方式去避免幻读,毕竟这样会不可避免地锁住一些非必要的区间,由于本人水平有限,不太明白,希望有懂得的人说一下。
参考文章:
狂乱的贵公子:MySQL事务隔离级别的实现原理
ameng:Innodb中的事务隔离级别和锁的关系
风雨之间:MySQL是如何解决不可重复读隔离级别中的幻读问题的
littlewhit:MySql进阶-间隙锁(gap-key)
邓小闲:答MySQL 是如何实现四大隔离级别的?