MVCC(Multi-Version Concurrency Control)即多版本并发控制,主要是为了提高数据库的并发性能。以下都是围绕InnoDB引擎来讲,因为MyIsam不支持事务。
同一行数据平时发生读写请求时,会上锁阻塞住。但MVCC用更好的方式去处理读-写
请求,做到在发生读-写
请求冲突时不用加锁。
这个读是指的快照读,而不是当前读,当前读是一种加锁操作,是悲观锁。
它读取的数据库记录,都是当前最新的版本,会对当前读取的数据进行加锁,防止其他事务修改数据。是悲观锁的一种操作。
如下操作都是当前读:
快照读的实现是基于多版本并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。
如下操作是快照读:
MVCC是“维持一个数据的多个版本,使读写操作没有冲突”的一个抽象概念。
这个概念需要具体功能去实现,这个具体实现就是快照读。
读-读
:不存在任何问题,也不需要并发控制读-写
:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读写-写
:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失MVCC用来解决读-写
冲突的无锁并发控制,就是为事务分配单项增长的时间戳。为每个数据修改保存一个版本,版本与事务时间戳相关联。
读操作只读取该事务开始前的数据库快照。
解决问题如下:
读-写
时:可以做到读操作不阻塞写操作,同时写操作也不会阻塞读操作。写-写
更新丢失问题。因此有了下面提高并发性能的组合拳:
MVCC + 悲观锁
:MVCC解决读写冲突,悲观锁解决写写冲突MVCC + 乐观锁
:MVCC解决读写冲突,乐观锁解决写写冲突主要是靠版本链、undo日志、Read View来实现的
数据库中的每行数据,除了我们肉眼看见的数据,还有几个隐藏字段:
db_trx_id
:6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID。db_roll_pointer
:7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)db_row_id
:6byte,隐含的自增ID(隐藏主键),如果数据表没有主键或者合适的唯一索引,InnoDB会自动以db_row_id产生一个聚簇索引。删除flag
隐藏字段,记录被更新或删除并不代表真的删除,而是删除flag
变了每次对数据库记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表,所以现在的情况就像下图一样:
对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id,这个信息很重要,在根据ReadView判断版本可见性的时候会用到。
undo log主要用于记录数据被修改之前的日志,在表信息修改之前先会把数据拷贝到undo log里。当事务进行回滚时可以通过undo log里的日志进行数据还原。
undo log的用途:
undo log主要分为两种:
事务进行快照读操作的时候会生产读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照。
记录并维护系统当前活跃事务的ID(没有commit,当每个事务开启时,都会被分配一个ID,这个ID是递增的,所以越新的事务,ID值越大),是系统中当前不应该被本事务看到的其他事务id列表。
Read View主要是用来做可见性判断的, 即当我们某个事务执行执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
Read View几个属性:
trx_ids
:当前系统活跃(未提交)事务版本号集合low_limit_id
:创建当前read view 时“当前系统最大事务版本号+1”up_limit_id
:: 创建当前read view 时“系统正处于活跃事务最小版本号”creator_trx_id
:创建当前read view的事务版本号Read View可见性判断条件
db_trx_id
< up_limit_id
|| db_trx_id
== creator_trx_id
(显示)
creator_trx_id
,那么说明这个数据就是当前事务自己生成的,自己生成的数据自己当然能看见,所以这种情况下此数据也是可以显示的。db_trx_id
>= low_limit_id
(不显示)
db_trx_id
是否在活跃事务(trx_ids)中
上面所讲的Read View用于支持RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别的实现。
RR、RC生成时机:
解决幻读问题:
RC、RR级别下的InnoDB快照读区别:
MVCC指的就是在使用RC、RR这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样可以使不同事务的读-写
、写-写
操作并发执行
,从而提升系统性能。