什么是当前读和快照度?

  • 当前读
    像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
  • 快照读

像不加锁的select 操作就是快照读,即不加锁的非阻塞读,快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读,之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

概念:

MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现
MVCC实现了读已提交和可重复读两种隔离级别,我个人感觉 读已提交和可重复读相当于两种概念,比如开关,但是真正实现的却是通过MVCC的工作机制在实现具体的隔离级别实现效果。

  • MVCC是InnoDB实现隔离级别的一种方式,基于读取已提交和可重复读两种隔离级别;
  • 对于读取未提交,直接读取最新可读版本的数据;
  • 对于串行化,使用加锁的方式访问记录;
  • 大多数事务型存储引擎实现都不是简单的行锁,基于并发性的考虑,一般会同时实现多版本并发控制(MVCC)处理读写冲突;
  • MVCC是乐观锁的一种实现,是通过保存数据在某一个时间点的快照实现的,写操作更新最新的版本,读操作读取旧版本;
  • MVCC中事务的修改操作(增删改)会为行记录新增一个版本快照,并把当前事务id写入trx_id;

所谓的MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用读已提交(READ COMMITTD)、可重复读(REPEATABLE READ)这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。

实现的依据就是版本号:

  • trx_id:对记录进行改动时,trx_id会记录当前事务id,也就是当前系统版本号;
  • roll_pointer:对记录进行改动,会把旧版本记录写入undo日志,roll_pointer指向修改之前的版本;
  • 对同一条记录的更新,会把旧值放到一条undo日志中,作为一个旧版本的记录,多次更新之后这些版本会被roll_pointer连接成一个链表,称之为版本链

如何读取版本:

当前读和快照读在RR级别下的区别:

表1:
image.png
表2:
image.png

而在表2这里的顺序中,事务B在事务A提交后的快照读和当前读都是实时的新数据400,这是为什么呢?

  • 这里与上表的唯一区别仅仅是表1的事务B在事务A修改金额前快照读过一次金额数据,而表2的事务B在事务A修改金额前没有进行过快照读。

所以我们知道事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读的地方非常关键,它有决定该事务后续快照读结果的能力
我们这里测试的是更新,同时删除和更新也是一样的,如果事务B的快照读是在事务A操作之后进行的,事务B的快照读也是能读取到最新的数据的。

在RR级别和RC级别的区别:

正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同

  • 在RR级别下的某个事务的对某条记录的第一次快照读会创建一个Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;
  • 即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见
  • 而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因

总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。

快照读和MVCC的关系:

  • 准确的说,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。仅仅是一个理想概念
  • 而在MySQL中,实现这么一个MVCC理想概念,我们就需要MySQL提供具体的功能去实现它,而快照读就是MySQL为我们实现MVCC理想模型的其中一个具体非阻塞读功能。而相对而言,当前读就是悲观锁的具体功能实现
  • 要说的再细致一些,快照读本身也是一个抽象概念,再深入研究。MVCC模型在MySQL中的具体实现则是由 3个隐式字段,undo日志 ,Read View 等去完成的

数据库并发带来的问题:

  • 读-读:不存在任何问题,也不需要并发控制
  • 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读

什么是MVCC:

MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的3个隐式字段,undo日志 Read View来实现的,目的在于提高数据库高并发场景下的吞吐性能。

  1. MySQLInnoDB 引擎支持 MVCC
  2. 应对高并发事务, MVCC 比单纯的加行锁更有效, 开销更小(为什么)
  3. MVCC 在读已提交(Read Committed)和可重复读(Repeatable Read)隔离级别下起作用
  4. MVCC 既可以基于乐观锁又可以基于悲观锁来实现

因为MVCC是运行在rc和rr两种隔离级别,那么他们区别是在哪里?

之前已经说过了rr和rc级别的快照读的区别

RR级别的ReadView生成时机:

RR 隔离级别下,每个事务 touch first read 时(本质上就是执行第一个 SELECT 语句时,后续所有的 SELECT 都是复用这个 ReadView

RC级别下的ReadView生成时机:

RC 隔离级别下,每个 **SELECT** 语句开始时,都会重新将当前系统中的所有的活跃事务拷贝到一个列表生成 **ReadView**。二者的区别就在于生成 ReadView 的时间点不同,一个是事务之后第一个 SELECT 语句开始、一个是事务中每条 SELECT 语句开始。

对于RC和RR在mvcc下的总结:

RCRR 两种隔离级别的事务在执行普通的读操作时,通过访问版本链的方法,使得事务间的读写操作得以并发执行,从而提升系统性能。RCRR 这两个隔离级别的一个很大不同就是生成 ReadView 的时间点不同,RC 在每一次 SELECT 语句前都会生成一个 ReadView,事务期间会更新,因此在其他事务提交前后所得到的 m_ids 列表可能发生变化,使得先前不可见的版本后续又突然可见了。而 RR 只在事务的第一个 SELECT 语句时生成一个 ReadView,事务操作期间不更新。

MVCC带来的好处:

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

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

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

image.png