Mysql事务隔离级别
介绍事务隔离级别之前,我们需要了解事务的一个概念
事务的ACID特性
- 原子性(Atomicity)
- 原子性,事务是数据库中的逻辑工作单位,事务中包含的操作要么全做,要么全不做
- 一致性(Consistency)
- 事务的执行结果必须是使数据库从一个一致性状态变为另一个一致性状态
- 隔离性(Isolation)
- 一个事务的执行不能被其它事务干扰,即一个事务的内部操作以及对数据的使用对其它并发事务应当是不可见的
- 持久性(Durability)
- 事务一旦提交,它对数据库中数据的改变应当是永久性的,接下来的其它操作与故障不应该对其执行结果有任何影响
事务的并发问题
脏读
事务A读取了事务B更新但未提交的数据,然后事务B回滚操作,那么事务A读取到的数据就是脏读
不可重复读
并发更新时,事务A多次读取同一数据,事务B在事务A多次读取的过程中对数据进行了修改并提交,导致事务A多次读取同一数据时获取的结果不一致
幻读
并发插入、删除时,事务A第一次读取到的数据和第二次读取到的数据数量不一致,事务B在事务A两次读取的过程中对数据进行了增加或删除的操作,导致A事务中前后读取到数据的数量发生了变化
四大特性中,redo log解决了持久性(Durability)问题,undo log解决了原子性(Atomicity),MVCC和锁解决了幻读问题,最终一起实现了终极目标——解决了一致性(Consistency)问题
Mysql事务的四种隔离级别
读未提交(Read Uncommit)
在该事务级别,所有事务都可以查询到其它未提交事务的执行结果,本隔离级别很少用于实际应用,因为性能也不比其它级别好多少
读已提交(Read Committed)
大多数数据库默认隔离级别(不是mysql默认的),它满足了隔离的简单定义:一个事务只能看到已经提交事务所作的改变,但是依旧会产生不可重复读,即多次查询同一数据可能会因为其它事务的提交而获得不同结果
可重复读(Repeatable Read)
这是mysql的默认事务隔离级别,在同一个事务内的查询都是事务开始时刻一致的
在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻读现象
但是innoDB解决了幻读,详细参考下文MMVC实现机制
RR级别如何解决不可重复度?
innodb采用了mvcc(多版本并发控制)来解决这一问题
mvcc是利用在每条数据后面加了隐藏的两列(创建版本号和删除版本号),每个事务在开始的时候都会有一个递增的版本号
查询操作为了避免查询到旧数据或已经被其他事务更改过的数据,需要满足如下条件:
查询时当前事务的版本号需要大于或等于创建版本号
查询时当前事务的版本号需要小于删除的版本号
即:
create_version <= current_version < delete_version
,这样就可以避免查询到其他事务修改的数据
可串行化(Serializable)
完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞,性能较差
在MySQL中,修复幻读的时候,并没有使用到串行化的事务隔离级别,而是使用了MVCC多版本并发控制和Next key lock临键锁的方式来修复幻读问题的
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(Read Uncommit) | √ | √ | √ |
读已提交(Read Committed) | × | √ | √ |
可重复读(Repeatable Read) | × | × | √ |
可串行化(Serializable) | × | × | × |
MySql的MVCC实现机制
快照读
单纯的SELECT语句(不加锁)都是快照读,但不包含下面两种
SELECT ... FOR UPDATE
SELECT ... LOCK IN SHARE MODE
快照读,读取的是记录数据的可见版本,有可能是历史数据, 不加锁,是非阻塞读
快照读sql提取的数据是MVCC根据readview来选择的
不同隔离级别下的快照读:
- Read Committed:每次select,都生成一个快照读
- 在事务中每一次执行快照读时生成ReadView
- Repeatable Read:开启事务后第一个select语句才是快照读的地方
- 仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView
- Serializable:快照读会退化为当前读
当前读
读取的是记录的最新版本,更新快照为最新的数据内容
所谓最新的数据内容是修改且已经提交的数据
读取时需要保证其它的并发事务不能修改当前记录,会对读取的记录进行加锁操作
在执行以下SQL语句时,会进行当前读:
select … lock in share mode
select … for update
update 、delete 、insert
MVCC是Multi-Version Concurrency Control(多版本并发控制)的缩写,MVCC没有统一的实现标准
不同的存储引擎对MVCC的实现方式是不同的,典型的有乐观并发控制和悲观并发控制
InnoDB对MVCC的实现采用的是乐观并发控制
MVCC会根据read_view中所保存的信息来构建当前事务可视版本
RC隔离级别产生不可重复度的原因
对于小于或者等于RC的隔离级别,事务开启后,每次执行SQL语句都会申请一个read_view,然后在执行完这个SQL语句后,调用read_view_close_for_mysql将read view从事务中删除,每次在执行SQL语句之前都会判断trx->read_view为空(理论下必为空),然后重新申请一个read_view(这就是为什么RC隔离级别下会产生不可重复读的原因)
RR隔离级别实现可重复读
对于RR隔离级别,当申请一个read_view后,事务未提交不会删除,整个事务将不再申请新的read_view,保证事务中所使用的read_view都是同一个,从而实现可重复读的隔离级别
如何解决幻读?
场景:
当前DB已有id 5, 10, 15三条数据。
事务A查询id < 10的数据,可以查出一行记录id = 5
事务B插入id = 6的数据
事务A再查询id < 10的数据,可以查出一行记录id = 5,查不出id = 6的数据(读场景,解决了幻读)
事务A可以执行更新/删除操作,然后查询id < 10 的数据,会发现可以查询到5和6两条数据(更新和删除操作进行了当前读,快照中的数据发生了变更)
RR级别下,快照读每次读取的都是第一次保存的快照,因此本身解决了因为其它事务造成的数据增加,删除而产生的幻读问题
但如果当前事务进行了增加,更新,删除操作之后快照中的数据发生了变化,就会在下次读取的时候出现幻读问题
- 由此可见,在标准的RR下,并没有彻底解决幻读问题,但是在Mysql的innodb引擎中使用 Next-Key lock(间隙锁(Gap锁)和行锁合称 next-key lock)加锁,读数据期间不允许写操作,从而彻底解决了幻读问题
快照读和当前读导致的幻读
这里讨论RR隔离级别下的幻读问题
1、如果事务中都是用快照读,那么不会产生幻读的问题
2、快照读和当前读一起使用的时候就会产生幻读
当前读触发的幻读问题,可以使用锁机制阻塞其它事务对间隙的操作(增加,删除),必须等待当前事务提交后,其它事务才能继续工作
使用MVCC+行锁+间隙锁
正常等值条件并且值存在的情况下加的是行锁,锁住某一行的数据
如果等值条件值不存在的情况下加的是间隙锁,或者范围查询,加的也是间隙锁
行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete,在RC、RR隔离级别下都支持
间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读,在RR隔离级别下都支持
临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap,在RR隔离级别下支持
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com