什么是mvcc
mvcc(多版本并发控制),作用在于可无锁实现隔离级别中的“可重复读” 提高数据库关于事务处理上的性能问题,其中"多版本" 指的是 UndoLog 链中的多个事务,“控制” 指的是我当前应当读取那个事务id对应的数据,大意就是“多个版本下(多个事务中)控制(指定)读取那个版本”。
直白的说mvcc 的作用就是使一个事务在执行的过程中对一条或多条查询出来的数据不管其他的事务或自己对这个数据如何修改,只要当前事务没有提交,事务里不管查几次数据都是一致的。
如:“一眼万年,初次见你那一眼便一生再也忘不掉”
先行知识点
要较深的理解MVCC,有几个知识点还是要先行了解的,事务的四大特性ACID、事务隔离级别、UndoLog 日志,读视图 redeViwe、当前读和快照读的概念 等等。下面先对以上做一个大致的讲述
1、事务的四大特性(ACID)
原子性(Atomicity): 事务的原子性是指事务必须是一个原子的操作序列单元。事务中包含的各项操作在一次执行过程中,只允许出现两种状态之一,要么都成功,要么都失败。任何一项操作都会导致整个事务的失败,同时其它已经被执行的操作都将被撤销并回滚,只有所有的操作全部成功,整个事务才算是成功完成。
一致性(Consistency): 事务的一致性是指事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处以一致性状态。
比如:如果从A账户转账到B账户,不可能因为A账户扣了钱,而B账户没有加钱
** 隔离性(Isolation):** 事务的隔离性是指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。也就是说,不同的事务并发操作相同的数据时,每个事务都有各自完整的数据空间。
一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务是不能互相干扰的
持久性(Duration): 事务的持久性是指事务一旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态
2、事务隔离级别
READ UNCOMMITTED(读未提交数据)
允许事务读取未被其他事务提交的变更
脏读,不可重复读和幻读 都会出现
READ COMMITED(读已提交数据)
只允许事务读取已经被其他事务提交的变更
可以避免 脏读 ,但 不可重复读和幻读 仍可能出现
REPEATABLE READ(可重复读)
确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新
可以避免 脏读和不可重复读,但仍可能出现 幻读
SERIALIZABLE(串行化)
确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入,更新和删除操作
所有并发问题 都可以避免,但性能十分低下
3、UndoLog 日志
UndoLog(撤销日志),在数据库事务开始之前,MYSQL会去记录更新前的数据到undo log文件中。如果事务回滚或者数据库崩溃时,可以利用undo log日志中记录的日志信息进行回退。
产生:在事务生成之前产生
删除:当事务提交之后,undo log并不能立马被删除,而是放入待清理的链表,由purge线程判断是否由其他事务在使用undo段中表的上一个事务之前的版本信息,决定是否可以清理undo log的日志空间
注意:undo log也会生产redo log,undo log也要实现持久性保护
4、读视图 ReadView
ReadView就是事务在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,InnoDB为每个事务构造了一个数组,用来记录并维护系统当前活跃事务的ID(“活跃”指的就是,启动了但还没提交)。
其中记录了 m_ids (当前活跃事务id) ,max_trx_id (下一个要分配的事务id)、min_trx_id(活跃事务中最小事务id)、creator_trx_id(当前读视图所属事务id)
== ReadView 在事务中第一个查询(select)执行时建立 ==
5、当前读与快照读
快照读: 根据undoLong 版本链 控制读取的版本,不能读到未提交事务数据。 select 都是快照读
当前读: update,delete,insert 都是当前读,读的是数据当前数据,是加锁防止并发冲突的
MVCC 详情
1、如下当前事物中有四个事物对同一条数据进行了修改,三个活跃中(未提交)一个已提交。当前的事物ID为7 的事物中是如何利用mvcc 读取数据的呢?
根据当前事物的读视图与undoLog 有如下四个判断条件:
1、判断当前版本是否为当前事物创建。(一个事物中自己改的自己当然能读)
2、当前事物ID是否小于最小事物ID。== 事物ID是自增的==(小于最小事物ID说明readview 生成的时候这个事物就提交了所以没有记录到,既然是提交过的当然可以读取)
3、当前事物ID是否大于最大事物ID。(大于最大事物ID,说明readview 生成时还没有这个事物ID,后来的事物中的数据肯定不能读取的啦)
4、以上都没有被确定读取则判断当前事物ID是否在m_ids中。(如果不在则说明这个版本已经提交了,可以读取)
== 如果当前roll_pointer 数据以上条件都未满足都不能读取则继续判断链条的下一条,直到满足条件==