数据库考点_9

InnoDB可重复读隔离级别下是如何避免幻读的(从两个方面来看)

从表象(我们实际看到的现象,不是真正的原因)看: 通过 基于伪MVCC实现的快照读(非阻塞读) 来避免让我们看到 幻行.

内在(真正的原因)看: 是由于next-key锁(行锁+gap锁).

当前读

-什么是当前读?

当前读指的是: 读取的是 记录的最新版本,并且读取之后还会对其加锁来保证其他并发事务不能修改当前记录.

-会出现当前读的几种语句:

1. select…lock in share mode(select语句默认是不加锁的)

2. select…for update

3. update, delete, insert

通过上面的语句我们可以知道: 其实当前读就可以理解为加了锁的增删改查语句

-update语句执行过程大致如图(第一次读取就是当前读):
在这里插入图片描述
结合数据库考点_8中关于幻读的例子,

通过上图就可以解释为什么在READ COMMITTED事务级别下会出现幻读的情况,因为读取到的是最新的数据版本,所以会出现事务A中执行更新语句时莫名其妙多了一行这种情况.

快照读

快照读指的是不加锁的非阻塞读,即在InnoDB中原始的select语句

注意: 这里的不加锁的条件,是基于事务隔离级别不为SERIALIZABLE串行化的条件之上的,当事务隔离级别为SERIALIZABLE时,由于是串行读,此时的快照读也会变成当前读,即在select语句后面自动加上lock in share mode或者for update

为什么会出现快照读

这主要是基于提升并发性能的考虑

快照读的实现: 基于多版本并发控制MCVV,我们可以认为MVCC是行级锁的一个变种,但是它避免了加锁操作,所以降低了开销,但是由于不加锁而且是基于多版本,所以可能导致读取到的数据不是最新版本.

一点注意事项:

  1. 读提交读未提交隔离级别下,快照读和当前读是一样效果的.

  2. 可重复读隔离级别下,当前读读取到的是最新数据版本,但是快照读有可能读取到的是之前的历史版本,也有可能读到最新版本,这个取决于另一个事务对记录的更新操作在当前事务执行查询操作之前还是之后, 更新操作在查询前的话当前读和快照读读取的数据就是一致的,反之就不是一致的,所以,在RR隔离级别下,调用快照读的时机很关键,直接决定了读取的数据版本..

    为什么? 因为当执行快照读的时机在另一个事务执行更新操作之前的话,另一个事务更新提交后,快照读读取的还是历史版本,但是如果是先让另一个事务执行更新操作并提交, 那么此时的快照读和当前读读取到的都是最新版本的数据.

RC,RR级别下InnoDB的快照读(非阻塞读)是如何实现的?

主要包括下面三个因子:

  1. 每行语句除了正常的数据记录外,还有额外的几个字段,最关键的是这三个:

    1. DB_TRX_ID(6字节,即数据库事务id): 用于标识最近一次对本行记录做的修改(不管是增还是改)的事务的id,**注意,**这里的delete操作,InnoDB也只是将其看成了一次update操作(用来更新行),它会给该行加上一个delete标记,在commit后才真正删除.
    2. DB_ROLL_PTR(7字节,数据库回滚指针):指向写入回滚段rollback segment的undo log日志记录,如如果一行记录被更新,则undo log中包含重建该行记录被更新前的内容所必需的信息.
    3. DB_ROW_ID(6字节,数据库行号):它包含一个随着行插入而单调递增的行id,当InnoDB自动产生聚簇索引时,它会放到聚簇索引中去,否则这个行id不会出现在任何索引中.
  2. 光有这几个字段还不足够实现快照读,还需要依托于undo日志(undo log):当我们对记录做了变更操作时,就会产生undo记录,里面存储着变更操作前的老版数据,当一个旧的事务需要读取老版数据时,为了能读取到老版本数据,就要顺着undo记录中的回滚指针去找到满足其可见性的记录.
    undo log 主要有两种:

    1. insert undo log :表示事务对insert操作产生的undo log,只在事务回滚时需要,并在在事务提交后便立即丢弃.
    2. update undo log :事务对记录进行delete或者update操作的时候产生,不仅在事务回滚时需要,快照读也需要,所以不能随便删除,只有当数据库所使用的快照中不涉及该日志记录,对应的回滚日志才会被删除.

通过实现更新行id为1的第二个字段从12变到32来演示日志的大概工作方式:
在这里插入图片描述

  1. 如图所示,首先InnoDB会用排它锁锁定该行.
  2. 然后,undo log会拷贝下更新前的行数据.
  3. 之后,修改当前行的值,随之填写当前事务id.
  4. 最后,让回滚指针指向undo log中存放修改前行数据的地方.

假设在这之后还有别的事务通过快照读来读取该日志记录,由于生成的undo log还没有被清除,是可以根据回滚指针读取到的
此时若某个事务又对同一行记录做了修改,此处将field3从13改为45,其执行过程和之前一样,只是回滚指针指向的地址会发生变化,它会按照修改的时间从近到远通过回滚指针记录下来,如图所示:
在这里插入图片描述

  1. 除了上述两个因子,还有最后一个: read view :主要用来做可见性判断,既当我们去执行快照读select的时候,会针对我们查询的数据创建一个read view来决定当前事务能看到的是哪个版本的数据,有可能是最新版的,也可能是undo log里面某个版本的数据.

read view遵循一个可见性算法(RR隔离级别下): 主要是将要修改数据的DB_TRX_ID取出来,与系统其它活跃事务id作对比,如果大于或者等于这些id的话,就通过回滚指针去取出undo log中上一层的DB_TRX_ID,直到修改数据的事务id小于这些活跃事务id为止,这样就保证了我们当前获取的数据版本是当前可见的最稳定的数据版本.

其中,越新开启(执行start transaction) 的事务,其事务id就越大.

正是因为生成时机的不同,造成了RC,RR两种隔离级别的不同可见性.

  1. 在RR隔离级别下,当执行START TRANSACTION开始事务后的第一条快照读之后,就会创建一个快照(即read view),将当前系统中活跃的其他事务记录起来,此后再调用快照读的时候用的还是同一个review,所以就会产生我们获取的数据有可能是最新的,也有可能是旧的,因为它会去找已提交的.
  2. 在RC隔离级别下,开启事务后,每次执行快照读语句的时候都会创建一个新的快照,这就解释了为什么我们在此隔离级别下,能看到其他事务对表的增删改了,已经提交的都是可见的.

号外

RU不使用read view,也不管什么乱七八糟的字段,直接读取最新的字段就好了.

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突(包括脏读,不可重复读,幻读),MVCC 主要应用于 Read Commited 和 Repeatable read 两个事务隔离级别,实质上使用的是快照数据,这样就可以实现不加锁读。

数据库并发场景有三种,分别为:

  1. 读-读:不存在任何问题,也不需要并发控制
  2. 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
  3. 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

所以MVCC可以为数据库解决以下问题:

在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能,同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

总之,MVCC就是因为大牛们,不满意只让数据库采用悲观锁这样性能不佳的形式去解决读-写冲突问题,而提出的解决方案,所以在数据库中,因为有了MVCC,所以我们可以形成两个组合:

MVCC + 悲观锁
MVCC解决读写冲突,悲观锁解决写写冲突

MVCC + 乐观锁
MVCC解决读写冲突,乐观锁解决写写冲突

这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值