什么是 MySQL InnoDB 的 MVCC?
MVCC
(Multi-Version Concurrency Control)是一种基于多版本的并发控制协议,只有在 InnoDB 引擎下存在。MVCC 是为了实现事务的隔离性,即通过版本号,避免同一数据在不同事务间的竞争,可以把它当成基于多版本号的一种乐观锁。当然,这种乐观锁只在事务级别读已提交(RC)和可重复读(RR)有效。MVCC 最大的好处,读不加锁,读写不冲突。在读多写少的应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能。
前置了解知识:
MySQL 的核心日志有哪些?
MySQL 中有七种日志文件,分别是:redo log(重做日志) 、undo log(回滚日志) 、bin log(二进制日志)、error log(错误日志)、slow query log(慢查询日志)、general log(一般查询日志),relay log(中继日志)
**bin log:**就是 binary log,二进制日志文件,记录了 MySQL 所有的 DDL 和 DML (除了数据查询语句)操作,以事件形式记录,还包含语句所执行的消耗的时间,MySQL 的二进制日志是事务安全型的。通过 bin log 日志我们可以做数据恢复,增量备份,主主复制和主从复制等等
undo log: 是 MySQL 用来记录事务操作的 反方向逻辑日志
,顾名思义,undo log是一种用于撤销回退的日志,在事务没提交之前,MySQL 会先记录更新前的数据到 undo log 日志文件里面,当事务回滚时或者数据库崩溃时,可以利用 undo log 来进行回退
redo log: 是 InnoDB 存储引擎产生的,记录事务对数据页的修改,如果 mysql 挂了,重启后 InnoDB 会使用redo log 恢复数据,保证了数据的持久性
数据库的事务特性 (ACID)有哪些?
-
原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么全部不执行
-
持久性(Durability): 对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障
-
隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的
-
一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序串行执行的结果相一致
MySQL 数据库的隔离级别有哪些,都会产生什么样的问题?
|-----------|---------------------------------------------------------------------------------|
| 脏读 | 脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据 |
| 不可重复读 | 不可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。 |
| 幻读 | 同样一笔查询在整个事务过程中多次执行后,查询所得的结果集是不一样的。也是指当事务不独立执行时,插入或者删除另一个事务当前影响的数据而发生的一种类似幻觉的现象。 |
什么是 MySQL InnoDB 当前读、快照读?
什么是当前读?
它读取的数据库记录,都是当前最新的版本,会对当前读取的数据进行加锁,防止其他事务修改数据。是悲观锁的一种操作。
如下操作都是当前读:
-
select lock in share mode(共享锁)
-
select for update(排他锁)
-
update(排他锁)
-
insert(排他锁)
-
delete(排他锁)
-
串行化事务隔离级别
什么是快照读?
快照读的实现是基于多版本并发控制,即 MVCC,既然是多版本,那么快照读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。普通的 select 查询语句(即不加锁的 select 操作)就是快照读
重要:后续图解会进行演示
Read Committed 隔离级别:每次 select 都生成一个快照读。
Read Repeatable 隔离级别:开启事务后第一个 select 语句才是快照读的地方,而不是一开启事务就快照读。即仅在第一次执行快照读时生成
为什么需要 MVCC?
- 读写锁的出现
读锁和读锁之间不互斥 ,而写锁和写锁、读锁都互斥。这样就很大提升了系统的并发能力。之后人们发现并发读还是不够
- MVCC 概念出现
能不能让读写之间也不冲突的方法,就是读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务 session 会看到自己特定版本的数据。当然快照是一种概念模型,不同的数据库可能用不同的方式来实现这种功能
总结 :MVCC
最大的优点是读不加锁,因此读写不冲突,并发性能好,类比 Java 中的读写锁,它是会存在读写竞争的,会有这个性能问题。MVCC 在 MySQL InnoDB 中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
MVCC 实现的原理
MySQL MVCC 实现原理:
具体实现主要是由隐式字段 + undolog 版本链 + read view 的方式实现
隐式字段:
-
DB_TRX_ID
:最近修改(修改/插入
)事务ID:记录创建这条记录/最后一次修改该记录的事务 ID,这个 id 是递增的 -
DB_ROLL_PTR
:回滚指针,指向这条记录的上一个版本
图解:
|------|------|-------------|---------------|
| id | name | DB_TRX_ID
| DB_ROLL_PTR
|
| 1088 | 张三 | 1 | 0x1232123 |
undolog 版本链是什么?
在每次更新该记录后,都会将旧值放到一条 undo 日志中。随着更新次数的增多,所有的版本都会被 roll_pointer 属性连接成一条链表,这个链表就称之为版本链。
图解:
Read View 是什么?
原文:
read view
An internal snapshot used by the MVCC mechanism of InnoDB. Certain transactions, depending on their isolation level, see the data values as they were at the time the transaction (or in some cases, the statement) started. Isolation levels that use a read view are REPEATABLE READ, READ COMMITTED, and READ UNCOMMITTED.
Read View是一个数据库的内部快照,该快照被用于 InnoDB 存储引擎中的 MVCC 机制。简单点说,Read View 就是一个快照,保存着数据库某个时刻的数据信息。Read View 会根据事务的隔离级别决定在某个事务开始时,该事务能看到什么信息。就是说通过 Read View,事务可以知道此时此刻能看到哪个版本的数据记录(有可能不是最新版本的,也有可能是最新版本的)。可重复读、读已提交、读未提交,这几个隔离级别都会使用 Read View
通俗点我的理解,Readview 是一个数据结构,包含四个字段,Read View 是"快照读" SQL 执行时 MVCC 提取数据的依据和规则
ReadView 包含的内容:
-
m_ids: 当前活跃的事务编号集合**,** 即还未提交。
-
**min_trx_id:**最小活跃事务编号
-
**max_trx_id:**预分配事务编号,当前最大事务编号 +1
-
**creator_trx_id:**ReadView 创建者的事务编号
ReadView 提取数据得规则:
1、被访问的 trx_id 与 readview 中的 creator_trx_id 相同,表示当前事务在访问自己修改的记录,可见,返回;
2、被访问的 trx_id 小于 min_trx_id,表明该版本已提交,可见,返回;
3、被访问的 trx_id 大于等于 max_trx_id ,表明该版本在生成 readview 时,还未开启,不可见,返回;
4、被访问的 trx_id 在 min_trx_id 和 max_trx_id 之间,判断是否在 m_ids 中,如果在,则说明生成 readview时,该版本事务未提交,该版本不可见;如果不在,则说明生成 readview 时,该版本事务已提交可见,返回。
案例图解说明:
思考:RC、RR 两种隔离级别下,两次查询的结果是什么?
答案:
RR 级别: select1="张三" select2="张三"
RC 级别 : select1="张三" select2="张小三" RC 隔离级别下出现了"不可重复读",后续讲解为什么
上述问题的产生以及 ReadView 解析:
RC 级别下得 read view 图解分析
1. 这是第两次我们进行 select 语句 read view 给我们生成的:
2. 第一次 select 语句分析图解:
3. 第二次 select 语句分析图解:
4. 产生的问题:
在 RC 隔离级别下,两次 select 语句读取到的内容不一致问题,即证明了 MySQL 数据库的隔离级为 RC 级别 下会产生不可重复读问题,再次证实前置了解知识模块中的不同隔离级别产生的问题所述。原因是 READ COMMITTD 在每一次进行普通 SELECT 操作前都会生成一个 ReadView
RR 级别下得 read view 图解分析
1. 连续多次快照读,ReadView 会产生复用,没有幻读问题
2. RR 级别下使用 MVCC 能避免幻读吗?
答案 :能,但不完全能!
特例:当两次快照读之间存在当前读,ReadView会重新生成,导致产生幻读
MVCC 总结:
所谓的MVCC(Multi-Version Concurrency Control ,多版本并发控制)
指的就是在使用读已提交(READ COMMITTD)、可重复读(REPEATABLE READ)
这两种隔离级别的事务在执行普通的 SELECT 操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。
这两个隔离级别的一个很大不同就是:生成ReadView的时机不同
,READ COMMITTD 在每一次进行普通 SELECT操作前都会生成一个 ReadView,而 REPEATABLE READ 只在第一次进行普通 SELECT 操作前生成一个ReadView,数据的可重复读其实就是 ReadView 的重复使用。
扩展知识:
- "当前读" 的实现是基于 next-key lock (行记录锁+Gap间隙锁)