最近通过《高性能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和文中所提到的乐观锁确实很像,它们到底是何关系我们还是无从所知。那我们再来看看《高性能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

InnoDB-MVCC与乐观锁的更多相关文章

  1. MySQL/InnoDB中,乐观锁、悲观锁、共享锁、排它锁、行锁、表锁、死锁概念的理解

    文章出处:https://www.souyunku.com/2018/07/30/mysql/?utm_source=tuicool&utm_medium=referral MySQL/Inn ...

  2. 【数据库】悲观锁与乐观锁与MySQL的MVCC实现简述

    悲观锁 悲观锁,就是一种悲观心态的锁,每次访问数据时都会锁定数据: 乐观锁 乐观锁,就是一种乐观心态的锁,每次访问数据时并不锁定数据,期待数据并没作修改,如果数据没被修改则作具体的业务 应用程序上使用 ...

  3. MYSQL中的乐观锁实现(MVCC)简析

    https://segmentfault.com/a/1190000009374567#articleHeader2 什么是MVCC MVCC即Multi-Version Concurrency Co ...

  4. 悲观锁,乐观锁以及MVCC

    在上文中,我们探讨了MySQL不同存储引擎中的各类锁,在这篇文章中我们将要讨论的是MySQL是如何实现并发控制的.并发问题有三种,分别为: 读-读,不存在任何问题 读-写,有隔离性问题,可能遇到脏读( ...

  5. innodb 悲观锁,乐观锁

    转 http://www.cnblogs.com/chenwenbiao/archive/2012/06/06/2537508.html CREATE TABLE `products` ( `id` ...

  6. 乐观锁和 MVCC 的区别?

    二者不是一个层面的东西. MVCC(Multi-Version Concurrent Control),基于快照隔离机制(Snapshot Isolations)进行多版本并发控制,是一种以乐观锁为理 ...

  7. InnoDB关于事务、锁、MVCC专题

    目录 并发所带来的的问题 脏写 脏读 不可重复读 幻读 事务 事务的特性 事务的四种隔离级别 锁 为什么要加锁 InnoDB的七种锁 不同事务RR和RC下加锁的规则 MVCC mvcc进一步提高并发 ...

  8. 通过版本号实现乐观锁(MVCC)

    乐观锁大多是基于数据版本记录的机制实现 , 如 , 为每一行数据增加一个整型版本标识(version) , 每次数据更新都把版本号+1 工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本 ...

  9. 【mysql】关于乐观锁

    一.乐观锁介绍 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检,乐观锁适用于 ...

随机推荐

  1. 如何使用PowerDesigner建表

    说明 个人认为,直接使用数据库管理工具如Navicat直接建表,如果后期需要进行库的迁移,不是那么方便,不如直接在PowerDesigner里面建表,更自由一些 版本:PowerDesigner15 ...

  2. Python基础——7面向对象高级编程

    实例与类动态添加方法 实例添加属性: def Student(object): pass s = Student() s.name = ‘syz’ 实例添加方法 from types import M ...

  3. P4554 小明的游戏

    SPFA板子题 #include <stdio.h> #include <string.h> #define Clean(X,K) memset(X,K,sizeof(X)) ...

  4. Example of DenseCRF with non-RGB data

    本笔记本通过一个示例说明如何在非rgb数据上使用DenseCRFs.同时,它将解释基本概念并通过一个示例进行演示,因此即使您正在处理RGB数据,它也可能是有用的,不过也请查看PyDenseCRF's ...

  5. AT24 I2C EEPROM解析及测试

    关键词:AT24.I2C.nvmem.EEPROM. 1. AT24C介绍 AT24C是一款采用I2C通信的EEPROM,相关驱动涉及到I2C和nvmem. I2C是读写数据的通道,nvmem将AT2 ...

  6. RabbitMQ学习笔记一:本地Windows环境安装RabbitMQ Server

    一:安装RabbitMQ需要先安装Erlang语言开发包,百度网盘地址:http://pan.baidu.com/s/1jH8S2u6.直接下载地址:http://erlang.org/downloa ...

  7. PS快速祛除脸上小雀斑

    首先我们要把图片放到PS软件中,然后在PS左侧工具栏中找到污点修复画笔工具(J), 配合着污点修复画笔中的修补工具一起使用,注意:模式要选择正常,属性栏中类型要选择内容识别. 下一步我们需要在图层上添 ...

  8. Word写博常用博客URL地址

    地址 描述 http://imguowei.blog.51cto.com/xmlrpc.php 51cto http://upload.move.blog.sina.com.cn/blog_rebui ...

  9. angular 4 router传递数据三种方法

    1.在查询参数中传递数据 <a  [routerLink]="['/product']" [queryParams]="{id:1,name:'dongian'}& ...

  10. SUCTF 2016 : dMd

    这个题可以说是比较坑了(还不是我很弱...) Linux跑一下: 要输密码 ida打开看看: int __cdecl main(int argc, const char **argv, const c ...