前言
RR解决脏读、不可重复读、幻读等问题,使用的是MVCC(Multi-Version Concurrency Control),即多版本的并发控制协议。在了解 MVCC 以前,咱们先来聊聊隐藏列、Undo log 和 Read View。html
隐藏列
InnoDB中每行数据除了咱们建立的字段外还有有隐藏列,其中隐藏列包含了本行数据的事务id、指向undo log的指针等。mysql
Undo log
MVCC 的实现是经过 Undo log 来完成的。当用户读取一行记录时,若该记录已经被其它事务占用,当前事务能够经过 Undo log 读取以前的行版本信息,由于没有事务须要对历史的数据进行修改操做,因此也不须要加锁,以此来实现非锁定读取。sql
Undo log(回滚日志)能够查看我以前整理的文章-----mysql日志文件总结数据库
Read View数组
什么是readview
事务(暂记事务1)在某一时刻给事务系统trx_sys打快照,把当时trx_sys状态(包括活跃读写事务数组)记下来,以后的全部读操做根据其事务id(即trx_id)与快照中的trx_sys的状态做比较,以此判断readview对于事务1的可见性。bash
Read View 中大体包含如下内容:
- trx_ids:数据库系统当前活跃事务 ID 集合;
- low_limit_id:活跃事务中最大的事务 ID +1;
- up_limt_id:活跃事务总最小的事务 ID;
- creator_trx_id:建立这个 Read View 的事务 ID。
好比某个事务,建立了 Read View,那么它的 creator_trx_id 就为这个事务的 ID,假如须要访问某一行,假设这一行记录的隐藏事务 ID 为 t_id,那么可能出现的状况以下:session
- 若是 t_id < up_limt_id,说明这行记录在这些活跃的事务建立以前就已经提交了,那么这一行记录对该事务是可见的。
- 若是 t_id >= low_limt_id,说明这行记录在这些活跃的事务开始以后建立的,那么这一行记录对该事物是不可见的。
- 若是 up_limit_id <= t_id < low_limit_id,说明这行记录多是在这些活跃的事务中建立的,若是 t_id 也同时在 trx_ids 中,则说明 t_id 还未提交,那么这一行记录对该事物是不可见的;若是 t_id 不在 trx_ids 中,则说明事务 t_id 已经提交了,那么这一行记录对该事物是可见的。
什么是MVCC
MVCC, 即多版本并发控制。MVCC 的实现,是经过保存数据在某个时间点的快照来实现的,也就是说,无论须要执行多长时间,每一个事务看到的数据都是一致的。根据事务开始的时间不一样,每一个事务对同一张表,同一时刻看到的数据多是不同的。并发
MVCC举例说明
# 首先开启两个临时客户端,将默认隔离级别改为RC mysql> set session transaction isolation level READ COMMITTED; Connection id: 7713 Current database: muke Query OK, 0 rows affected (0.06 sec) mysql> select @@tx_isolation; +----------------+ | @@tx_isolation | +----------------+ | READ-COMMITTED | +----------------+ 1 row in set, 1 warning (0.00 sec) # 客户端1 mysql> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) # 首先查看t21 b为4的数据 mysql> select * from t21 where b = 4; +----+---+---+ | id | a | b | +----+---+---+ | 4 | 4 | 4 | +----+---+---+ 1 rows in set (0.00 sec) mysql> update t21 set b = 44 where b = 4; Query OK, 1 rows affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select sleep(10); +-----------+ | sleep(10) | +-----------+ | 0 | +-----------+ 1 row in set (10.01 sec) mysql> rollback; Query OK, 0 rows affected (0.01 sec) # 客户端2 mysql> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) mysql> select * from t21 where b = 4; +----+---+---+ | id | a | b | +----+---+---+ | 4 | 4 | 4 | +----+---+---+ 1 rows in set (0.00 sec) # 在客户端执行select sleep(10);在执行查询语句 mysql> select * from t21 where b = 4; +----+---+---+ | id | a | b | +----+---+---+ | 4 | 4 | 4 | +----+---+---+ 1 rows in set (0.00 sec) mysql> rollback; Query OK, 0 rows affected (0.00 sec)
从上面执行现象能够看出:客户端1的事务先于客户端2的事务执行,可是发如今客户端1休眠的时候,事务2很快就执行完毕并返回告终果。 若是RC模式下读操做须要获取S锁,可是由于当前已经有了X锁,两个锁是互斥的,理论上S锁须要等待X锁释放才会获取,可是并无发生这种状况,能够看出读操做并无使用S锁, 而是使用MVCC实现的。源码分析
在实验中,客户端2 查询的结果是 客户端1 修改以前的记录,也就是那个点的 Read View,根据上面将的 Read View 原理,被查询行的隐藏事务 ID 就在当前活跃事务 ID 集合中。所以,这一行记录对该事物(客户端2中的事务)是不可见的,能够知道 客户端2 查询的 b=4 记录实际就是来自 Undo log 中。咱们看到的现象就是同一条记录在系统中存在了多个版本,这就是多版本并发控制(MVCC)。性能
图例
扩展知识
- RR隔离级别(除了Gap锁(间隙锁)以外)和RC隔离级别的差异是建立snapshot时机不一样。 RR隔离级别是在事务开始时刻,确切地说是第一个读操做建立read view的;RC隔离级别是在语句开始时刻建立read view的。
- 建立/关闭read view须要持有mutex,会下降系统性能,5.7版本对此进行优化,在事务提交时session会cache只读事务的read view。
- 下次建立read view,判断若是是只读事务而且系统的读写事务状态没有发生变化,即trx_sys的max_trx_id没有向前推动,并且没有新的读写事务产生,就能够重用上次的read view。
- MVCC 最大的好处是读不加锁,读写不冲突,极大地增长了 MySQL 的并发性。经过 MVCC,保证了事务隔离性。
- MVCC 为何只在 RC 和 RR 两个隔离级别下工做?
RU: 每次只取最新的值,不存在多个值的状况。 串行:每次查询会加读锁,当有更新时会阻塞住,只有等到查询完锁释放后,才会作更新操做,由此可知RU和串行的隔离级别下,只会存在一个值,不存在多个值的状况。MVCC是解决并发状况下,同时可能产生多个值,从多个值中查询出一个准确值的技术,因此RU,串行是用不到MVCC技术
学习连接
MySQL · 源码分析 · InnoDB的read view,回滚段和purge过程简介
人们觉得他们的理性支配言语,恰恰有时反而支配理性。 ---培根