引言
今天和大家一起聊一聊mysql中的MVCC机制,如果没有看过官方文档的, 建议先看一下官方的文档 Mysql官方文档MVCC
好了废话不多说,我们正式开始
「阅读大概需要5分钟左右」
什么是MVCC?
MVCC全称为multiversion concurrency control,翻译过来的就是多版本并发控制。
这个多版本并发控制怎么理解?
从字面意思上来说就是通过数据多个版本的管理来处理并发问题。这说的是不是有点抽象, 如果觉得还不能够理解的话,可以暂时不要纠结这个名字的意思, 我们来聊一聊这个MVCC是干嘛的
MVCC的出现主要是为了解决在 innodb存储引擎下MySql并发读写的性能问题。这个地方有个重点就是他只是针对innodb这种 存储引擎,对MyISAM这种存储引擎没有效果的,因为对于MyISAM这个存储引擎来说,他的锁机制是 「只支持表级别的锁,当 写入数据时,数据库会锁整个表」
当数据库锁整个表时,在事物隔离级别中属于串行,串行就不存在什么并发处理能力了。而MVCC就是为了解决数据库并发读写性能问题 的同时还能保证事物的一致性。这个时候你明白了MVCC是干嘛的了吗?
MVCC可以解决事物的哪些隔离级别问题?
首先我们先来聊一聊什么是事物?
它存在的意义就是为了保证所有的数据都是符合期望的,且相互关联的数据之间不会产生矛盾,即数据状态的一致性(Consistency).按照数据库的 经典理论,要达成这个目标,需要三方面共同努力来保证【原子性,一致性,持久性】。
- 原子性(atomicity):这个理解起来很简单就是操作要么成功要么失败,不存在部分成功部分失败的情况,通过undo日志来进行回滚操作。
- 一致性(consistency):一致性是指事物执行前后,数据库的数据满足预定义的规则和约束,保持数据的逻辑完整性和一致性. 比如,一个表的字段不能为负数, 那么任何事物都不能使这个字段的值变为负数.一致性是事物的目的,而原子性,隔离性和持久性是实现一致性的手段。
- 隔离性(isolation):隔离性是指在并发事物中,一个事物的执行不受其他事物的影响,即一个事物内部操作和使用的数据对其他并发事物是隔离的。,它可以保证事物的正确性和一致性。 通过MVCC和锁的方式实现
- 持久性(durability):持久性是指事物一旦提交,它对数据库的改变是永久的,即使发生系统崩溃或者其他故障,也不会影响事物的结果。持久性是事物的重要特征之一,它可以保证数据的安全性和完整性。 主要通过redo log(重做日志)来实现。
隔离级别
事物的隔离级别可以分为:读未提交,读已提交,可重复读,串行。
不同的隔离级别可以解决不同的问题:
- 读未提交:无法解决脏读,重复读和幻读的问题;
- 读已提交:可以解决脏读,但是不能解决重复读和幻读的问题;
- 可重复读:可以解决脏读,和重复读的问题,但是不能彻底解决幻读的问题;
- 串行:可以解决读未提交,读已提交,可重复读,幻读等问题。
MVCC解决了哪些隔离级别的问题
-
如果在读已提交的隔离级别条件下MVCC是可以解决脏读的问题。
-
如果在可重复读的的隔离级别条件下MVCC是可以解决脏读和可重复读以及部分幻读的情况。
MVCC是怎么解决这些问题的?
MVCC通过一致性视图(consistent read)和回滚日志(undo log)来实现在不加锁的情况下来实现「读已提交」和「可重复读」的隔离级别问题;
什么是一致性视图?
一致性视图(consistent read)大家可以看一下mysql官方的文档:
1.一致性读视图是建立在快照的基础上(snapshot)
2.在可重复读的隔离级别下一致性读视图是在第一次进行读取的时候就生成了。这个地方有个需要注意的地方: 如果事物是只读事物,那么一致性视图在事物开始时就生成了,并且在整个事物期间不会改变。 如果事物是读写事物,那么一致性视图在事物开始时生成,但是每当事物执行一个更新语句,一致性视图就会被重置。这是为了 事物可以看到自己对数据所做的修改。
MVCC是怎么通过一致性视图来实现的?
首先需要了解的一点就是在内部,InnoDB会向数据库中存储的每一行添加三个字段:
DB_TRX_ID: 占6个字节,该字段表示插入或者更新的最后一个事物的事物标识符号;
DB_ROLL_PTR: 占7个字节,该字段指向了上一个撤销日志(undo log),这样就现成了一个undo log的版本链;
DB_ROW_ID: 占6个字节,DB_ROW_ID是一个由Innodb自动生成的字段,它用来表示每一行的唯一标识,它的值会随着 新行的插入而递增.这个地方有个需要注意的地方就是如果表中有聚集索引(表中有主键和唯一非空索引),
那么InnoDB就不会生成DB_ROW_ID这个字段,也就不会在任何索引中存储DB_ROW_ID的值,因为InnoDB可以直接使用主键或唯一非空索引作为聚集索引, 不需要额外的字段来标识每一行,也就是说DB_ROW_ID这个字段只有在表中没有主键或者唯一非空索引的情况下才会存在,否则就不存在。
一致性视图是通过使用ReadView这个数据结构来实现的,在ReadView中包含了以下几个重要的信息:
- creator_trx_id:创建这个ReadView的事物ID;
- low_limit_id:当前系统中已经分配出去的最大事物的ID,也就是下一个事物ID的值;
- up_limit_id:当前系统中已经提交的最小事物ID,也就是最早启动但还没提交的事物ID;
一致性视图的可见性规则
- 如果记录的版本号(即最后修改该记录的事物ID)小于ReadView的up_limit_id,说明该记录是在当前事物之前就已经提交了,所以对当前事物不可见.
- 如果记录的版本号大于等于ReadView的low_limit_id,说明该记录是在当前事物之后才修改的,所以对当前事物不可见。
- 如果记录的版本号等于ReadView的creator_trx_id,说明该记录是当前事物自己修改的,所以对当前事物是可见的.
- 如果记录的版本号在ReadView的up_limit_id和low_limit_id之间,那么就要看这个版本号是不是在trx_ids里面, 如果是在这个数组里面,说明这个记录是被其他活跃事物修改的,所以对当前事物是不可见的;如果该版本号不在数组中,说明该记录 是被其他已提交事物修改的,所以对当前事物是可见的。
重点:如果一个记录对当前事物不可见,那么就需要从undo日志中找到该记录在当前事物开始时的版本,并返回给当前事物,这样就保证了同一个事物多次读取同一个记录时结果是一致的(隔离级别在可重复读)。
待解决问题
-
up_limit_id:当前系统中已经提交的最小事物ID,也就是最早启动但还没提交的事物ID,这句话怎么理解?
-
MVCC怎么解决幻读的问题?