最近通过《高性能MySQL》一书学习MySQL方面的知识,在看到书中所讲InnoDB-MVCC部分的时候,有一种强烈的感觉,这不就是乐观锁吗(入门级小学徒的疑惑脸)?当下便去网上以各种方式查找阅读MVCC和乐观锁相关的博客,发现大部分的博客对于这两者之间的关系都只字不提,提了的也是众说纷纭,关于两者关系的细节方面也十分暧昧没有定论。在暂时无法得出最终结论的情况下,我先谈谈在学习这方面知识后我自己对两者的理解,然后试着得出自己的结论,正确与否大家一起思考。

在解释MVCC之前,我首先引用《高性能MySQL》书中原文来解释一下隔离级别:

READ UNCOMMITTED(未提交读):在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读。

READ COMMITTED(提交读):大多数数据库默认的隔离级别(MySQL除外)。在READ COMMITTED级别,一个事务开始时,只能“看见”已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的,这个级别有时候也叫做不可重复读,因为同一事务中两次执行同样的查询,可能会得到不一样的结果。

REPEATABLE READ(可重复读):这是MySQL默认的隔离级别,解决了脏读的问题。该级别保证了在同一事务中多次读取同样记录的结果是一致的。但是理论上,该隔离级别还是无法解决另外一个幻读的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行。

SERIALIZABLE(可串行化):该隔离级别下通过强制事务串行执行,避免了幻读的问题。简单来说,SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。

上面解释隔离级别时提到了,在REPEATABLE READ隔离级别下,尽管解决了不可重复读,但还是存在幻读的问题。如果要避免幻读,就得在事务执行的时候加锁,但是大量的锁会严重影响性能。怎样才能不通过加锁还能解决幻读呢?这就是MVCC要做的事情。

MVCC是Multi-Version Concurrency Control(多版本并发控制)的缩写,很多数据库都实现了MVCC,但是在不同的存储引擎中MVCC的实现是不同的,今天所说的是InnoDB中的MVCC实现。InnoDB的MVCC,是通过在每行记录后保存两个隐藏的列来实现的(用户不可见)。一个列保存行创建的时间,一个列保存行过期(删除)的时间,这里所说的时间并不是传统意义上的时间,而是系统版本号,下面是REPEATABLE READ隔离级别下MVCC的具体操作:
-SELECT
InnoDB会根据以下两个条件检查每行记录:
(1)InnoDB只查找版本早于当前事务版本的数据行(行的系统版本号小于或者等于事务的系统版本号),这样可以确保事务读取到的行,要么是在事务开始之前已经存在的,要么是事务自身插入或者修改过的(结合以下INSERT、UPDATE操作理解)。
(2)行的删除版本要么未定义,要么大于当前事务版本号。可以确保事务读取到的行,在事务开启之前未被删除(结合以下DELETE操作理解)。
-INSERT
InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
-DELETE
InnoDB为删除的每一行保存当前系统版本号作为行删除标识(第二个隐藏列的作用来了)。
-UPDATE
InnoDB将更新后的列作为新的行插入数据库(并不是覆盖),并保存当前系统版本号作为该行的行版本号,同时保存当前系统版本号到原来的行作为行删除标识。

到这里,MVCC是什么以及它做了什么事基本上已经说清楚了,为什么在学习了MVCC后我会产生“这就是乐观锁”的想法呢(实际上很多人都有这种想法,在一些博客里也有人说MVCC就是乐观锁)?有这几个原因。首先,InnoDB中MVCC和乐观锁(其实这么说是不严谨的,后面会解释为什么)都是通过“不加锁”的手段来实现加锁的效果。其次它们的不加锁手段都是通过版本号去控制的。通过这两点也不难看出为什么会有很多人在MVCC和乐观锁之间产生疑问。

那么乐观锁是怎么实现的呢?最常见的就是通过数据版本(Version)记录机制实现。数据版本和InnoDB-MVCC中的系统版本作用相似不做过多解释。通过为数据库表增加一个数字类型的字段作为版本标识Version(用户可见,字段名自定),当读取数据时,将其Version的值一同读出,数据每更新一次,Version都增加1,当提交更新的时候,判断数据库表对应行的当前版本信息与第一次读取出来的Version值进行对比,如果一致,则给与更新,否则不予更新(可以不涉及事务,但是MVCC机制必须依托于事务,事实上隔离级别本就是事务的隔离级别)。具体操作如下:

SELECT id, name, Version FROM testable;(例如id=1,Version=1024)
UPDATE testable SET name=’张三’,Version=Version+1 WHERE id=1 AND Version=1024;

InnoDB-MVCC与乐观锁-LMLPHPInnoDB-MVCC与乐观锁-LMLPHP

从上面的所有文字中,我们还是无法得出一个有效的结论,只看得出InnoDB-MVCC和文中所提到的乐观锁确实很像,它们到底是何关系我们还是无从所知。那我们再来看看《高性能MySQL》中所提到的一句话:不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制。看完这句话我们再结合上文,可以得出这样一个结论:MVCC并不是乐观锁,InnoDB所实现的MVCC才是乐观锁(当然也有其他存储引擎利用乐观并发控制的思想实现MVCC),更严谨一点来说,乐观锁并不是一种具体的技术,乐观锁只是一种并发控制的思想,所有认为“并发事务不算大”而采用非加锁的形式来实现“加锁”效果的控制机制我们都认为它是乐观锁。既然如此,那我们之前提到的乐观锁就不能叫乐观锁了,它只是乐观锁的一种表达方式,是一种在用户行为上通过非加锁的方式来实现并发控制的手段。同样的MVCC更不能称之为乐观锁,只能说InnoDB实现的MVCC是一种在系统行为上通过非加锁的方式来实现并发控制的手段。

总结来说,InnoDB-MVCC是一种系统行为,在REPEATABLE READ隔离级别下,它通过乐观并发控制解决了该隔离级别所不能解决的幻读,但是前提是这些都得依托于事务的封装。尽管如此,它还是无法完全解决一些并发业务场景下的问题,并且过多的事务使用会严重影响系统的性能,这就需要通过用户行为去约束(最开始所提到的乐观锁)。

所以,最后的结论就是,MVCC并非乐观锁,但是InnoDB存储引擎所实现的MVCC是乐观的,它和之前所提到的用户行为的“乐观锁”都采用的是乐观机制,属于不同的“乐观锁”手段,它们都是“乐观家族”的成员。

原文:https://blog.csdn.net/Jeaforea/article/details/82181449

知乎:

在数据库中,并发控制是指在多个用户/进程/线程同时对数据库进行操作时,如何保证事务的一致性和隔离性的,同时最大程度地并发。

当多个用户/进程/线程同时对数据库进行操作时,会出现3种冲突情形:

  1. 读-读,不存在任何问题
  2. 读-写,有隔离性问题,可能遇到脏读(会读到未提交的数据) ,幻影读等。
  3. 写-写,可能丢失更新

要解决冲突,一种办法是是锁,即基于锁的并发控制,比如2PL,这种方式开销比较高,而且无法避免死锁。

多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读

乐观并发控制(OCC)是一种用来解决写-写冲突的无锁并发控制,认为事务间争用没有那么多,所以先进行修改,在提交事务前,检查一下事务开始后,有没有新提交改变,如果没有就提交,如果有就放弃并重试。乐观并发控制类似自选锁。乐观并发控制适用于低数据争用,写冲突比较少的环境。

多版本并发控制可以结合基于锁的并发控制来解决写-写冲突,即MVCC+2PL,也可以结合乐观并发控制来解决写-写冲突。

 
链接:https://www.zhihu.com/question/27876575/answer/71836010
05-11 11:25