MySQL可重复读隔离级别能解决幻读吗?

事务及事务隔离级别

innodb存储引擎支持事务,myisam不支持事务

事务内的操作要么全部成功,要么全部失败,中途有失败则回滚

事务的ACID:原子性,一致性,隔离性,持久性

事务隔离级别需要解决的问题:

  • 脏读:读到其他事务未提交的事务

  • 不可重复读:同一事务内,不同时刻读到的同一批数据可能不一样

  • 幻读:对于插入来说的,事务A按照一定条件进行数据读取,期间事务B插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现事务B新插入的数据

事务隔离级别:

  • 读未提交

  • 读提交

  • 可重复读(默认隔离级别)

  • 串行化

从上至下隔离强度逐渐增强

读未提交:不加锁,连脏读问题都解决不了

读提交:解决了脏读,但是还有不可重复读的问题。因为可能某个事务查询期间,有另一个事务修改并提交了数据,则可能导致同一事务两次数据读到的不一样。

可重复读:事务不会读到其他事务对已有数据的修改,事务开始有什么数据,那么事务提交前的任意时刻,数据都是一样的。但是,对于事务新插入的数据是可以读到的,这也就是幻读的问题。但是需要强调的是,MySQL的可重复读其实解决了幻读的问题。

串行化:问题都解决了,但是将事务的执行变为了顺序查询,相当于当线程。

MySQL如何实现事务隔离级别?

读未提交:不加锁,可以理解为没有隔离。

串行化:读的时候加共享锁,写的时候加排他锁。

剩下的两个既要允许一定的并发,又要解决一些问题,因此比较复杂。

MVCC

MySQL采用MVCC(多版本并发控制)的方式,实现读提交和可重复读

对于一个快照来说,能读到哪些基于以下规则:

  1. 当前事务内的更新,可以读到

  2. 版本未提交,不能读到

  3. 版本已提交,但是在快照创建后提交的,不能读到。

  4. 版本已提交,且是在快照创建前提交的,可以读到。

对于快照读(select)

对于读提交,每次进行快照读时都会生成最新的Read View

对于可重复读,只有事务开始的第一个快照读会创建Read View

对于当前读(update,insert,delete)

行锁加间隙锁来解决,这个合并的锁叫做Next-Key锁

在修改的记录两边的区间加上间隙锁。

这里需要注意一个问题,加锁的过程要分为有索引和无索引两种情况,有索引时,直接通过索引找到数据行,直接加锁。对于没有索引的,会为这张表所有行加上锁,不满足条件的释放锁,留下满足条件的,会极大影响性能。

漏洞?:默认隔离级别(可重复读)

  1. 事务A select

  2. 事务B insert

  3. 事务B commit

  4. 事务A select(与第一次select的结果一样)

  5. 事务A update 所有行

  6. 事务A select(会发现多出了事务B insert 的行)

这算出现了幻读吗?

所以说MVCC并不能完美解决RR下的幻读问题??

一旦事务A的修改操作覆盖到了其他事务插入的“幻行”,那么在下次select的时候,也会把这行数据一起查出来

要解释这个现象,需要深入理解MVCC

隐藏字段

  1. DB_TRX_ID(6字节):表示最近一次对本记录行作修改(insert | update)的事务ID。至于delete操作,InnoDB认为是一个update操作,不过会更新一个另外的删除位,将行表示为deleted。并非真正删除。

  1. DB_ROLL_PTR(7字节):回滚指针,指向当前记录行的undo log信息

  2. DB_ROW_ID(6字节):随着新行插入而单调递增的行ID。理解:当表没有主键或唯一非空索引时,innodb就会使用这个行ID自动产生聚簇索引。如果表有主键或唯一非空索引,聚簇索引就不会包含这个行ID了。

Read View结构

low_limit_id:目前出现过的最大的事务ID+1,即下一个将被分配的事务ID

up_limit_id:活跃事务列表trx_ids中最小的事务ID,如果trx_ids为空,则up_limit_id 为 low_limit_id

trx_ids:Read View创建时其他未提交的活跃事务ID列表。

creator_trx_id:当前创建事务的ID,是一个递增的编号

比较算法

db_trx_id < up_limit_id || db_trx_id == creator_trx_id(显示)

如果数据事务ID小于read view中的最小活跃事务ID,则可以肯定该数据是在当前事务启之前就已经存在了的,所以可以显示。

或者数据的事务ID等于creator_trx_id ,那么说明这个数据就是当前事务自己生成的,自己生成的数据自己当然能看见,所以这种情况下此数据也是可以显示的。

db_trx_id >= low_limit_id(不显示)

如果数据事务ID大于read view 中的当前系统的最大事务ID,则说明该数据是在当前read view 创建之后才产生的,所以数据不显示。如果小于则进入下一个判断

db_trx_id是否在活跃事务(trx_ids)中

不存在:则说明read view产生的时候事务已经commit了,这种情况数据则可以显示。

已存在:则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的。

实际例子

MySQL可重复读隔离级别能解决幻读吗?_第1张图片

 

对应的事务10003的实际运行的三次select(原始数据id=1,age=19)

MySQL可重复读隔离级别能解决幻读吗?_第2张图片

 

那么这个叫出现了幻读吗?

快照读和当前读混用造成的异常,不能算是出现了幻读

当前读的效果就是要读取最新版本,实际上是把隔离级别从 repeatable-read 降级到了 read-committed,所以快照读和当前读混用不算幻读。

MySQL可重复读隔离级别能解决幻读吗?_第3张图片

我们是不是得说这个例子说明了 repeatable-read 允许“不可重复读异常现象”?显然不能。对于不可重复读异常,repeatable-read 隔离级别是明确不允许的。这就说明了当前读和快照读混用带来的异常不能称为幻读。

总结:

  1. MVCC能实现快照读的可重复读,不会出现不可重复读和幻读

  2. 间隙锁加行锁(或者说Next-Key锁),实现当前读的可重复读,也不会出现不可重复读和幻读

  3. 快照读和当前读混用造成的异常,不能算是出现了幻读

你可能感兴趣的:(mysql,mysql,数据库,database)