今年开始,自己开始修炼储存与消息相关的技术”内功”,想着在当下的开发工作与未来的路途发展中,这两大块是无论如何都无法避开的,所以就开始加强:Mysql、redis与Mq。Mysql的文章看至一半,前几天在我们几个的技术群里,和另外一个小伙伴,就两个核心的日志文件,展开了争论:到底和事务相关的是redolog还是undolog呢?本来自己很了解来着,没想到,最终竟然搞混了!实在惊叹于Mysql的设计。今天我就用Mysql如何使用undolog这个日志,来说一说非常难懂且核心的Mysql技术点:MVCC(多版本并发控制)。注意:整篇文章使用Mysql默认的存储引擎InnoDB来讲解,具体不做与MyIASM的对比。

一、一些基础概念的铺垫

要理解MVCC如何工作,必须要掌握一些Mysql的基础概念,其中包括:事务隔离级别、锁(共享锁,排它锁)

1、事务隔离级别

这一点,可能是各位开发人员烂熟于心的知识点:读未提交(ru)、读已提交(rc)、可重复读(rr)、序列化(serializable)。具体的表现形式,下面来说说:

  • 读未提交:一个事务还没提交时,它做的变更就能被别的事务看。就是说本事务开启之后的任何修改,能立刻被其他事务读取到
  • 读已提交:一个事务对一个值的修改,必须等到此事务提交之后,这个被修改的值才能被别的事务读取到
  • 可重复读:一个事务开启之时,就保证一条数据的一致性,直到此事务结束。即使事务过程中,有其他事务对此条数据进行了修改,本事务也是不可见的。
  • 序列化:这一点很好理解,每次事务开启,都对数据进行加锁,其他事务要等此事务结束,才能进行操作

四种隔离级别是依次变严格的,当然性能也是依次下降的。所引发的问题类似于:脏读、不可重复读、幻读等等都是一些具体的场景,我这里用一个统一的两事务统一场景,来说明一下,具体到底什么事脏读,什么是不可重复读,我不一一举例(幻读要到后面讲间隙锁的时候,才能涉及),下面是例子:

  • 若隔离级别是“读未提交”, 则 V1 的值就是 2。这时候事务B还没提交,但是在这个隔离级别下面,对于事务A来说已经是可看到的了,所以V2、V3都是2了
  • 若隔离级别是”读已提交”,由于事务B未提交,所以对于字段修改,其他事务是不可见的,所以事务A中V1的值是1,而当事务A到了V2查询之时,事务B已经提交了,所以V2,V3的值都是2
  • 隔离级别是”可重复读”,根据这个隔离级别的描述,由于事务A从开启到提交,都是统一的视图,所以,事务A中的V1、V2的值都是1,虽然过程中事务B对值进行了修改,而且也提交了,但是对于事务A中,还是不可见的,当然到了V3的时候,事务A提交,当然就可以看到修改的值,所以是2
  • 若隔离级别是”序列化”,那就简单了,事务A一开始就所记录进行了加锁,然后事务B被阻塞,事务A里面的V1、V2都是1,事务提交之后,启动事务B,又对记录加了锁,然后事务执行update,提交事务B之后,才能查到值V3,结果是2

整个四大隔离级别,用这一个例子就能完美地说清了~另外Mysql默认是处于第三隔离级别的(可重复读)

2、锁(共享锁,排它锁)

涉及到的两种类型的锁主要如下:

  • 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
  • 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。
  • 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
  • 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

这些锁,前面两个是针对行记录,后面两个针对整表的。具体各种锁的兼容情况如下:

如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。

意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。

  • 共享锁(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE。
  • 排他锁(X):SELECT * FROM table_name WHERE … FOR UPDATE。

用SELECT … IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT… FOR UPDATE方式获得排他锁。

下面是针对两种行级别的锁(共享锁与排它锁),做的一些实验:

以上就是两个行级锁的实践,具体的,InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

二、我们来看MVCC

Multi-Version Concurrency Control 多版本并发控制(MVCC),是Mysql中InnoDB这个存储引擎实现事物隔离级别的主要手段~这里要强调InnoDB的原因是,主要实现事务,是通过存储引擎实现的,而Mysql本来是不具备这个功能的。

在InnoDB这个里面,主要就是使用MVCC的整个逻辑,来实现事物的第三隔离级别的,就是实现并发控制。具体的做法,我使用我自己的语言,来尽量简要地写写,都是基于原理的一些讲说,涉及在深入的,例如如何进行命令的查看,如何看mvcc的c++实现源码,暂时能力还不到那个级别。下面我分小节,一步步来说说这个原理

1、创建基础的实验数据表

mysql> CREATE TABLE `t` ( `id` int(11) NOT NULL, `k` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB; insert into t(id, k) values(1,1),(2,2);

2、给出我们操作的流程

3、引出快照与undolog理念

快照,在innoDB里面叫:consistent read view(一致性视图),是用来实现重复读与读已提交的主要手段。其原理就是每次事务启动的时候,都会对整个库,创建一个快照,其实我自己的理解,就是对整个数据库创建一个内存空间(开始并非主动全部加载数据页到内存),接下来每次的修改,都是直接针对内存里面数值的修改(当然第一次sql执行,要进行数据页的磁盘加载操作),这样就能非常高效且多线程的进行修改了!

每次修改一条数据的时候,内存中会加载这条数据的页,然后创建一个视图,这个视图内部保存了当前已经修改成功的值,和数据库为这个事务自动申请的事务Id,记为row tx_id,然后记录一条能够回滚到上个修改记录的日志:undolog。下面就是一个具体的示意图:

这里v1、v2、v3、v4就是我们所说的快照!而这里u1、u2、u3就是我们所说的undolog。真实的,内存中只保留最新的修改数据,就是上图的v4,如果事物10想读取k的值,并且v2、v3、v4这三个视图对应的事物,都没有提交的话,是要顺序执行u1、u2、u3这三个undolog的,这样就能实现第三事物级别的可重复读。这里我们注意,我加了前缀定语:v2,v3,v4都没有提交!后面会马上说到原因!

4、update与”一致读”

每次进行update记录之前,都要进行一致读,所谓的一致读,其实就是读所读记录加上一章所说的排它锁,这个排它锁,使得这个记录每次读取的都是最新的数据。那如果这样,就说明一个问题,如果其他事务首先进行对这个字段的update,就会首先加排它锁,其他的事务再次去update的时候,就必须等待。等他这个锁释放。那什么时候释放呢?显然,必须要等其他事务结束的时候,下面用具体的实例说明,我们还是使用第一个小节给出的数据表进行说明:

5、update之后如何读取数值

这里就会涉及到一些mvcc的核心理念,据说内部c++实现是非常生涩的,这里我针对我读到的逻辑进行了简化,进行讲解。

我们的问题是:针对一条数据,同时存在多个版本,那我们在一个事物里面,每次select(不加锁)读取到的值到底是什么版本的呢?而一个事务中,又是如何感知其他事务更新的呢?

其实,InnoDB为每个事务创建一个数组,在每次事务启动的时候,保存当前mysql系统中针对这一条数据,活跃的(就是还没提交)事务id。每次更新这条数据,都会拿最新的内存快照(这一条数据),对快照中的row tx_id进行判断,具体的判断逻辑如下:

  • 这个id在快照表中,说明是还未提交的事务进行的更新,不能用,使用undolog回滚到上一个视图,查找上一个版本中的tx_id
  • 这个id不在快照表中,那就要看当前事务与所比对视图的id值当前事务的id比视图中的事务id值大,说明视图的是在当前事务开始之前创建更新并提交的,那这个值是可以使用的,有效的,返回当前事务的id比视图中的事务id值小,说明视图是在当前事务开始之后开始的事务,就是说,当前事务开启的时候,系统中活跃的事务并没有这个视图,这个视图对应的事务是在之后才开始的,所以这个视图里面针对这个记录的更新,对于当前事务是不可见的。同样,使用undolog回滚到上一个视图,继续按照这个套路查找。

整个视图+当前事务+undolog的使用原理,大致如此

6、看看第2小节的结果

我们来看看第2小节中,select语句查询的k值,分别是多少,按照5中的分析,一点点地往上捋。我们先做如下的假设:

  1. 事务 A 开始前,系统里面只有一个活跃事务 ID 是 99;
  2. 事务 A、B、C 的版本号分别是 100、101、102,且当前系统中只有这四个事务
  3. 三个事务开始前,(1,1)这一行数据的 row trx_id是90

如此的话,那么事务A启动时候,活跃视图数组值是:[99,100];事务B启动时候的数组是:[99,100,101];事务C启动时候,活跃数组值是:[99,100,101,102]。下面是整个更新视图创建过程图:

我们使用第5小节中的分析,我们来看一下,事务A中的get k这个值,到底是怎么获取的:

  • 首先查找到系统中这条记录的最新的视图记录,101这个版本,发现,101的这个事务id,不在当前事务的活跃视图数组中,且比当前事务id要大,不可见,使用undolog向上,到102这个版本
  • 102这个版本的视图,里面的事务id是102,同样的,102也是不在活跃数组中且比当前事务id要大,同样是不可见的,照样的使用undolog向上查找上一个版本:90
  • 发现90这个版本的视图中事务Id值也不在活跃数组当中,但是这个id值比当前事务的id值要小,所以这个值可见,返回90这个版本视图中的k的值1

整个过程如此,其实写下来发现,并没有想象的那么复杂。是不?其实InnoDB中,完全就是使用这一套的逻辑,”通杀”的!包括读提交这个隔离级别,在这个隔离级别下,无非就是创建视图的时机在每次update的时候罢了,其他查找判断逻辑和我们这里讨论的一模一样!

mysql update 锁_Mysql心路历程:两个”log”引发的”血案”相关推荐

  1. mysql viewlog_Mysql心路历程:两个”log”引发的”血案”

    今年开始,自己开始修炼储存与消息相关的技术"内功",想着在当下的开发工作与未来的路途发展中,这两大块是无论如何都无法避开的,所以就开始加强:Mysql.redis与Mq.Mysql ...

  2. mysql update锁_mysql中update语句的锁

    UPDATE rent_contacts SET contacts_mobile='11111' WHERE  rent_unit_code in (SELECT rent_unit_code FRO ...

  3. mysql 时区时间_MySql的时区(serverTimezone)引发的血案

    前言 mysql8.x的jdbc升级了,增加了时区(serverTimezone)属性,并且不允许为空. 血案现场 配置jdbc的URL:jdbc:mysql://[IP]:[PORT]/[DB]?c ...

  4. mysql timezone上海_MySql的时区(serverTimezone)引发的血案

    前言 mysql8.x的jdbc升级了,增加了时区(serverTimezone)属性,并且不允许为空. 血案现场 配置jdbc的URL:jdbc:mysql://[IP]:[PORT]/[DB]?c ...

  5. mysql a锁_MYSQL中的锁

    前言 刚开始接触MYSQL,对其锁机制并不了解,在项目里面,针对死锁以及锁竞争,约定了两条规则. 对涉及多个业务表的更新,要遵守一定的顺序,如按照TABLE-A,TABLE-B,TABLE-C的次序 ...

  6. mysql mdl 锁_MySQL源码学习:MDL字典锁

    什么是MDL MDL,Meta Data lock,元数据锁,一般称为字典锁.字典锁与数据锁相对应.字典锁是为了保护数据对象被改变,一般是一些DDL会对字典对象改变,如两个TX,TX1先查询表,然后T ...

  7. mysql innodb 锁_MySQL/InnoDB锁机制

    显式加锁 select ... lock in share mode:加 S 锁 select ... for update:加 X 锁 MySQL快照读和当前读 在一个支持MVCC并发控制的系统中, ...

  8. mysql unique 锁_mysql 行锁排查

    mysql 锁表: 隔离级别使用RR: mysql> SELECT @@GLOBAL.tx_isolation, @@tx_isolation; +----------------------- ...

  9. mysql 全局锁_Mysql全局锁和表级锁

    以前对Mysql的锁的认识,只了解表锁和行锁,其实Mysql的锁的种类还是不少的,有全局锁,表级锁,行级锁,还有元数据锁,间隙锁,临界锁. 一 全局锁 Mysql的全局锁是对整个实例加锁,加锁之后,数 ...

最新文章

  1. 掌握生信技术,玩转生态与地理领域的科研套路
  2. PHP 函数dirname()使用实例
  3. 执行系统命令,subprocess使用说明
  4. docker run -it 如何退出_Docker学习笔记(4)容器的基本命令 1 - 你个小秃头
  5. mysql 权限管理 目录
  6. wxWidgets:wxRichTextCtrl类用法
  7. mysql自动写入创建时间_mysql 自动记录数据插入及最后修改时间
  8. BZOJ3511: 土地划分
  9. Hibernate使用原生SQL适应复杂数据查询
  10. jQueryEasyUI选项卡 - 自定义工具样式
  11. eclipse拒绝mysql,eclipse连接mysql的有关问题
  12. Python依赖文件requirements.txt的生成和安装
  13. Linux 线程如何实现同步与互斥
  14. Qt窗口操作函数(最大化,全屏,隐藏最大化,最小化)
  15. davinci项目服务器无法,【工程管理】为达芬奇建一个项目管理服务器 多人协同调色...
  16. Git(4):提交代码时忽略不必要的文件或文件夹
  17. 什么是磁力链接如何愉快的使用磁力链接
  18. crmeb java单商户源码java二开文档部署文档H5商城部署文档【5】
  19. vba 抓取php网页,用VBA操作网页并抓取数据
  20. 制作PPT的常用网站及一些基本原则

热门文章

  1. centos开启防火墙指定端口
  2. npm 卸载_手把手教你创建一个NPM包
  3. oracle物理备份与恢复,Oracle 备份与恢复概念原理学习
  4. 五行塔怎么吃第五个_红毛丹怎么吃 吃红毛丹的五个好处
  5. 计算机专业的学员为什么要学erp,对要进入计算机专业学习的人的小小建议(浅谈)...
  6. android 关于页面,解析android中的帮助、about、关于作者、HELP等提示页面
  7. Hbuilder实用技巧
  8. Java项目代码结构
  9. 送大家一份Latex模板呢~!(编译即用)
  10. python【力扣LeetCode算法题库】206-反转链表