什么是MySQL MVCC的ReadView?

背景

MySQL InnoDB RR隔离级别是否能够避免幻读?

我们都知道,在MySQL InnoDB中,支持四种事物隔离级别,分别为:

1、READ UNCOMMITED(未提交读):使用查询语句不会加锁,允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。

2、READ COMMITED(提交读):只能读取到已经提交的数据,只对记录加记录锁,而不会在记录之间加间隙锁,所以允许新的记录插入到被锁定记录的附近,所以再多次使用查询语句时,可能得到不同的结果(Non-Repeatable Read)。

3、REPEATABLE READ(可重复读):多次读取同一范围的数据会返回第一次查询的快照,会返回相同的数据行。

4、SERIALIZABLE(串行读):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

其中,REPEATABLE READ(下文中简称RR) 是MySQL的默认事务隔离级别,而RR的核心实现机制,是基于MVCC(Multi-Version Concurrency Control) 机制来进行实现的。

MVCC中,非常重要的一个实现机制则是依赖于ReadView,那么什么是MVCC的 ReadView,本篇我们就来一起探讨一下。

MySQL InnoDB MVCC机制

在前面的文章中,我们介绍过MySQL的MVCC机制,这里我们做个简单回顾。

MVCC多版本控制指的是一种提高并发的技术。

最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。

引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。

InnoDB的一致性的非锁定读就是通过MVCC实现的,MySQL的大多数事务型存储引擎实现的都不是简单的行级锁。

基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。

MVCC的实现,是通过保存数据在某一个时间点的快照来实现的。因此每一个事务无论执行多长时间看到的数据,都是一样的。

MVCC实现细节

MVCC的核心实现主要基于两部分:多事务并发操作数据与一致性读实现

多事务并发操作数据

多事务并发操作数据核心基于Undo log进行实现,Undo log可以用来做事务的回滚操作,保证事务的原子性。

同时可以用来构建数据修改之前的版本,支持多版本读。

InnoDB中,每一行记录都有两个隐藏列:DATA_TRX_ID和DATA_ROLL_PTR。(若没有主键,则还有一个隐藏主键)

  • DATA_TRX_ID:记录最近更新这条记录的事务ID(6字节)
  • DATA_ROLL_PTR:指向该行回滚段的指针,通过指针找到之前版本,通过链表形式组织(7字节)
  • DB_ROW_ID:行标识(隐藏单增ID),没有主键时主动生成(6字节)

当存在多个事务进行并发操作数据时,不同事务对同一行的更新操作产生多个版本,通过回滚指针将这些版本链接成一条Undo Log链。

操作过程如下:

  • 1、将待操作的行加排他锁。

  • 2、将该行原本的值拷贝到Undo Log中,DB_TRX_IDDB_ROLL_PTR保持不变。(形成历史版本)

  • 3、修改该行的值,更新该行的DATA_TRX_ID为当前操作事务的事务ID,将DATA_ROLL_PTR指向第二步拷贝到Undo Log链中的旧版本记录。(通过DB_ROLL_PTR可以找到历史记录)

  • 4、记录Redo Log,包括Undo Log中的修改。

  • INSERT操作:产生新的记录,其DATA_TRX_ID为当前插入记录的事务ID。

  • DELETE操作:软删除,将DATA_TRX_ID记录下删除该记录的事务ID,真正删除操作在事务提交时完成。

一致性读实现

在InnoDB中,对于不同的事务隔离级别,一致性读实现均不相同,具体如下:

  • READ UNCOMMITED隔离级别:直接读取版本的最新记录。
  • SERIALIZABLE隔离级别:通过加锁互斥访问数据实现。
  • READ COMMITEDREPEATABLE READ隔离级别:使用版本链实现。(ReadView,可读性视图)

对于RCRR隔离级别,实现一致性读都是通过ReadView,也就是今天的重点,什么是ReadView

MVCC ReadView

ReadView是事务开启时,当前所有活跃事务(还未提交的事务)的一个集合,ReadView数据结构决定了不同事务隔离级别下,数据的可见性。

MySQL官方文档说明

Read view lists the trx ids of those transactions for which a consistent read should not see the modifications to the database.

ReadView的数据结构如下示例:
ReadView

ReadView的组成:

up_limit_id:
The read should see all trx ids which are strictly smaller (<) than this value.
In other words, this is the low water mark".

low_limit_id:
The read should not see any transaction with trx id >= this value.
In other words, this is the “high water mark”.

m_ids:
Set of RW transactions that was active when this snapshot was taken.

  • up_limit_id:最先开始的事务,该SQL启动时,当前事务链表中最小的事务id编号,也就是当前系统中创建最早但还未提交的事务
  • low_limit_id:最后开始的事务,该SQL启动时,当前事务链表中最大的事务id编号,也就是最近创建的除自身以外最大事务编号
  • m_ids:当前活跃事务ID列表,所有事务链表中事务的id集合

注:ID越小,事务开始的越早;ID越大,事务开始的越晚

OK,ReadVeiw核心数据结构如上所示,我们来解读一下两个核心字段low_limit_idup_limit_id


首先需要明确两个重点概念(敲黑板!!!):

  • 1、下面所说的db_trx_id,是来自于数据行中的db_trx_id字段,并非开启了一个事务分配的ID,分配的事务ID只有操作了数据行,才会更新数据行中的db_trx_id字段
  • 2、ReadView是与SQL绑定的,而并不是事务,所以即使在同一个事务中,每次SQL启动时构造的ReadViewup_trx_idlow_trx_id也都是不一样的

up_limit_id表示“低水位”,即当时活跃事务列表的最小事务id(最早创建的事务),如果读取出来的数据行上的的db_trx_id小于up_limit_id,则说明这条记录的最后修改在ReadView创建之前,因此这条记录可以被看见。

if (trx_id < view->up_limit_id) {
	return(TRUE);
}

low_limit_id表示“高水位”,即当前活跃事务的最大id(最晚创建的事务),如果读取出来的数据行上的的db_trx_id大于low_limit_id,则说明这条记录的最后修改在ReadView创建之后,因此这条记录肯定不可以被看见。

if (trx_id > view->low_limit_id) {
	return(FALSE);
}

如果读取出来的数据行上的的db_trx_idlow_limit_idup_limit_id之间,则查找该数据上的db_trx_id是否在ReadViewm_ids列表中:

  • 如果存在,则表示这条记录的最后修改是在ReadView创建之时,被另外一个活跃事务所修改,所以这条记录也不可以被看见。
  • 如果不存在,则表示这条记录的最后修改在ReadView创建之前,所以可以看到。

这里非常的绕,如果没看懂,请您多读几遍,仔细理解一下这个过程。

不同的事务隔离级别下,生成ReadView的时机则各不相同,下面我们分别来看看一下RRRCReadView

REPEATABLE READ下的ReadView生成

每个事务首次执行SELECT语句时,会将当前系统所有活跃事务拷贝到一个列表中生成ReadView

每个事务后续的SELECT操作复用其之前生成的ReadView

UPDATE,DELETE,INSERT对一致性读snapshot无影响。

示例:事务A,B同时操作同一行数据

  • 若事务A的第一个SELECT在事务B提交之前进行,则即使事务B修改记录后先于事务A进行提交,事务A后续的SELECT操作也无法读到事务B修改后的数据。
  • 若事务A的第一个SELECT在事务B修改数据并提交事务之后,则事务A能读到事务B的修改。

针对RR隔离级别,在第一次创建ReadView后,这个ReadView就会一直持续到事务结束,也就是说在事务执行过程中,数据的可见性不会变,所以在事务内部不会出现不一致的情况。

READ COMMITED下的ReadView生成

每次SELECT执行,都会重新将当前系统中的所有活跃事务拷贝到一个列表中生成ReadView

针对RC隔离级别,事务中的每个查询语句都单独构建一个ReadView,所以如果两个查询之间有事务提交了,两个查询读出来的结果就不一样。

总结一下:
RC的本质:每一条SELECT都可以看到其他已经提交的事务对数据的修改,只要事务提交,其结果都可见,与事务开始的先后顺序无关。
RR的本质:第一条SELECT生成ReadView前,已经提交的事务的修改可见。

从这里可以看出,在InnoDB中,RR隔离级别的效率是比RC隔离级别的高。

此外,针对RU隔离级别,由于不会去检查可见性,所以在一条SQL中也会读到不一致的数据。

针对串行化隔离级别,InnoDB是通过锁机制来实现的,而不是通过多版本控制的机制,所以性能很差。

结语

本篇,我们了解了InnoDB中的MVCC的实现机制,这里来总结一下:

  • MVCC的核心实现主要基于两部分:多事务并发操作数据与一致性读实现
  • 多事务并发操作数据核心基于Undo Log进行实现
  • 一致性读实现核心基于ReadView进行实现

关于MVCC的实现,还是极为复杂的,本文仅谈了一点皮毛,如果您对更多的核心细节感兴趣,还是建议阅读官方文档或其他权威资料,感谢您的阅读。

本篇参考:
ReadView Class Reference
MySQL · 引擎特性 · InnoDB 事务系统
MySQL InnoDB MVCC实现
MySQL中的MVCC

### 回答1: MySQLRead view是InnoDB存储引擎实现MVCC(多版本并发控制)的一部分。它主要用于支持在并发取事务数据时保证数据的一致性。在MVCC中,每个事务都可以看到数据库中的一个快照版本。在并发取时,Read view可以确保每个事务看到的数据版本是相同的,从而确保数据的一致性Read view是一个由系统版本号和活动事务列表组成的数据结构。系统版本号表示当前数据库中的最新版本,而活动事务列表则包含正在运行的事务和已提交但尚未释放其快照版本的事务。在取数据时,MySQL会使用当前的Read view来确定事务所能看到的数据版本,并且只返回在此版本之前提交的数据。 因此,Read viewMySQL中起着非常重要的作用,能够保证数据的一致性和并发性能。 ### 回答2: MySQLRead view视图)是一种用于查询事务中数据的逻辑快照。它的作用主要有以下几点。 首先,Read view用于解决并发事务中的写冲突。当一个事务开始时,它会创建一个自己的Read view,该视图包含了当前事务开始时数据库中数据的一个快照。其他并发事务可以继续对数据库进行增删改操作,但取该数据的事务只能取到Read view中的数据。这样可以保证取事务在并发环境下不会到其他未提交的事务的脏数据,确保操作的一致性和准确性。 其次,Read view用于实现事务的隔离级别。MySQL支持多种事务隔离级别,包括未提交、提交、可重复和串行化。每种隔离级别在Read view中有不同的实现方式,从而保证不同隔离级别下的事务取到的数据是符合要求的。 另外,Read view还可以用于MVCC(多版本并发控制)机制。MVCC在数据库中可以实现事务的并发控制,通过为每个事务分配唯一的事务标识和Read view来实现。Read view中记录了每个事务开始时数据库中数据的一个快照,可以对比该快照和数据库当前的版本,判断其他事务的写冲突,进而进行相应的并发控制,提高数据库的并发性能。 总之,MySQLRead view在并发事务中起到了保证操作的一致性和隔离性的作用,同时也是实现MVCC机制的重要组成部分。通过使用Read view,可以使得事务在取数据时不会受到其他并发事务的干扰,并保持数据库一致性。 ### 回答3: MySQLRead view是指在并发取过程中,为每个事务提供一致的取快照。它的作用主要体现在以下几个方面: 首先,Read view可以解决取过程中的脏问题。当一个事务在执行过程中,其他事务可能会对相同的数据进行修改,如果没有Read view,就可能导致取到未提交的脏数据。而通过使用Read view,每个事务都能取到其他事务已提交的数据,避免了脏问题的发生。 其次,Read view可以提供一致的取视图。当一个事务开始时,Read view会记录下事务开始时数据库中所有数据的版本号。事务执行过程中,如果其他事务对数据进行了修改并提交了,那么这个事务仍然看到的是开始时的数据版本,保证了事务内部的一致性。 此外,Read view还可以提高并发取的效率。在MySQL中,Read view通过MVCC(多版本并发控制)来实现。每个事务根据开始时的版本号,只取那些已提交版本中的数据,可以避免了锁机制的使用,从而提高了并发取的效率。 总的来说,MySQLRead view提供了一致的取视图,解决了并发取过程中可能出现的脏问题,同时还提高了并发取的效率。它是保证数据的一致性和并发性的重要机制之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值