上节回顾


上篇记录了我对MySQL 事务 隔离级别** read uncommitted **的理解。
这篇记录我对 MySQL 事务隔离级别 read committed & MVCC 的理解。

前言


可以很负责任的跟大家说,MySQL 中的此隔离级别不单单是通过加锁实现的,实际上还有repeatable read 隔离级别,其实这两个隔离级别效果的实现还需要一个辅助,这个辅助就是MVCC-多版本并发控制,但其实它又不是严格意义上的多版本并发控制,是不是很懵,没关系,我们一一剖析。

目录


1.单纯加锁是怎么实现 read committed 的?
2.真实的演示情况是什么样子的?
3.MVCC 实现原理?
4.对于InnoDB MVCC 实现原理的反思
5.福利彩蛋

1.单纯加锁是怎么实现 read committed 的?


从此隔离级别效果入手:事务只能读其他事务已提交的的记录。
数据库事务隔离级别的实现,InnoDB 支持行级锁,写时加的是行级排他锁(X lock),那么当其他事务访问另一个事务正在update (除select操作外其他操作本质上都是写操作)的同一条记录时,事务的读操作会被阻塞。所以只能等到记录(其实是索引上的锁)上的排他锁释放后才能进行访问,也就是事务提交的时候。这样确实能实现read commited隔离级别效果。
数据库这样做确实可以实现 事务只能读其他事务已提交的的记录 的效果,但是这是很低效的一种做法,为什么呢?因为对于大部分应用来说,读操作是多于写操作的,当写操作加锁时,那么读操作全部被阻塞,这样会导致应用的相应能力受数据库的牵制。

2.真实的演示情况是什么样子的?


看如下操作:
1.开启两个客户端实例,设置事务隔离级别为read committed,并各自开启事务。

 set transaction isolation level read committed;set autocommit = 0;begin;

2.客户端1做更新操作:

update test set name = '测试' where id =32;

结果如下图所示:

屏幕快照 2017-03-28 下午1.39.44.png

3.客户端2做查询操作:

select name from test where id = 32;

结果如下所示:

屏幕快照 2017-03-28 下午1.41.23.png

这时估计你有疑问了,正在 被客户端1 upate 的记录,客户端2还能无阻塞的读到,而且读到的是未更改之前的数据。
那就是 InnoDB 的辅助打得好,因为内部使用了 MVCC 机制,实现了一致性非阻塞读,大大提高了并发读写效率,写不影响读,且读到的事记录的镜像版本。

下面开始介绍 MVCC 原理。

3.MVCC 实现原理


网上对 MVCC 实现原理 的讲述五花八门,良莠不齐。
包括《高性能MySQL》对 MVCC 的讲解只是停留在表象,并没有结合源码去分析。当然绝大多数人还是相信这本书的,从来没有进行深剖,思考。
如下是 《高性能MySQL》对 MVCC实现原理 的描述:

"InnoDB的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列,
一个保存了行的创建时间,一个保存了行的过期时间,
当然存储的并不是实际的时间值,而是系统版本号。"

就是这本书,蒙蔽了真理,害人不浅。

我们还是看源码吧:


1.记录的隐藏列
其实有三列

在Mysql中MVCC是在Innodb存储引擎中得到支持的,Innodb为每行记录都实现了三个隐藏字段:6字节的事务ID(DB_TRX_ID)7字节的回滚指针(DB_ROLL_PTR)隐藏的ID6字节的事物ID用来标识该行所述的事务,7字节的回滚指针需要了解下Innodb的事务模型。

2.MVCC 实现的依赖项
MVCC 在mysql 中的实现依赖的是 undo log 与 read view。

1.undo log: undo log中记录的是数据表记录行的多个版本,也就是事务执行过程中的回滚段,其实就是MVCC 中的一行原始数据的多个版本镜像数据。
2.read view: 主要用来判断当前版本数据的可见性。

3.undo log

undo log是为回滚而用,具体内容就是copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。undo buffer与redo buffer一样,也是环形缓冲,但当缓冲满的时候,undo buffer中的内容会也会被刷新到磁盘;与redo log不同的是,磁盘上不存在单独的undo log文件,所有的undo log均存放在主ibd数据文件中(表空间),即使客户端设置了每表一个数据文件也是如此。

我们通过行的更新过程来看下undo log 是如何形成的?

3.1 行的更新过程
下面演示下事务对某行记录的更新过程:

  1. 初始数据行

    F1~F6是某行列的名字,1~6是其对应的数据。后面三个隐含字段分别对应该行的事务号和回滚指针,假如这条数据是刚INSERT的,可以认为ID为1,其他两个字段为空。
    2.事务1更改该行的各字段的值

    当事务1更改该行的值时,会进行如下操作:
    用排他锁锁定该行

    记录redo log
    把该行修改前的值Copy到undo log,即上图中下面的行
    修改当前行的值,填写事务编号,使回滚指针指向undo log中的修改前的行
    3.事务2修改该行的值

    与事务1相同,此时undo log,中有有两行记录,并且通过回滚指针连在一起。

4.read view 判断当前版本数据项是否可见

在innodb中,创建一个新事务的时候,innodb会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(read view),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。当用户在这个事务中要读取该行记录的时候,innodb会将该行当前的版本号与该read view进行比较。
具体的算法如下:

  1. 设该行的当前事务id为trx_id_0,read view中最早的事务id为trx_id_1, 最迟的事务id为trx_id_2。
  2. 如果trx_id_0< trx_id_1的话,那么表明该行记录所在的事务已经在本次新事务创建之前就提交了,所以该行记录的当前值是可见的。跳到步骤6.
  3. 如果trx_id_0>trx_id_2的话,那么表明该行记录所在的事务在本次新事务创建之后才开启,所以该行记录的当前值不可见.跳到步骤5。
  4. 如果trx_id_1<=trx_id_0<=trx_id_2, 那么表明该行记录所在事务在本次新事务创建的时候处于活动状态,从trx_id_1到trx_id_2进行遍历,如果trx_id_0等于他们之中的某个事务id的话,那么不可见。跳到步骤5.
  5. 从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo-log的版本号,将它赋值该trx_id_0,然后跳到步骤2.
  6. 将该可见行的值返回。

需要注意的是,新建事务(当前事务)与正在内存中commit 的事务不在活跃事务链表中。

对应代码如下:

函数:read_view_sees_trx_id。
read_view中保存了当前全局的事务的范围:
【low_limit_id, up_limit_id】
1. 当行记录的事务ID小于当前系统的最小活动id,就是可见的。
  if (trx_id < view->up_limit_id) {
    return(TRUE);
  }
2. 当行记录的事务ID大于当前系统的最大活动id,就是不可见的。
  if (trx_id >= view->low_limit_id) {
    return(FALSE);
  }
3. 当行记录的事务ID在活动范围之中时,判断是否在活动链表中,如果在就不可见,如果不在就是可见的。
  for (i = 0; i < n_ids; i++) {
    trx_id_t view_trx_id
      = read_view_get_nth_trx_id(view, n_ids - i - 1);
    if (trx_id <= view_trx_id) {
    return(trx_id != view_trx_id);
    }
  }

5 事务隔离级别的影响

但是:对于两张不同的事务隔离级别
  tx_isolation='READ-COMMITTED': 语句级别的一致性:只要当前语句执行前已经提交的数据都是可见的。
  tx_isolation='REPEATABLE-READ'; 语句级别的一致性:只要是当前事务执行前已经提交的数据都是可见的。
针对这两张事务的隔离级别,使用相同的可见性判断逻辑是如何做到不同的可见性的呢?

6.不同隔离级别下read view的生成原则

这里就要看看read_view的生成机制:
1. read-commited:
  函数:ha_innobase::external_lock
  if (trx->isolation_level <= TRX_ISO_READ_COMMITTED
    && trx->global_read_view) {
    /* At low transaction isolation levels we let
    each consistent read set its own snapshot */
  read_view_close_for_mysql(trx);
即:在每次语句执行的过程中,都关闭read_view, 重新在row_search_for_mysql函数中创建当前的一份read_view。
这样就可以根据当前的全局事务链表创建read_view的事务区间,实现read committed隔离级别。
2. repeatable read:
  在repeatable read的隔离级别下,创建事务trx结构的时候,就生成了当前的global read view。

使用trx_assign_read_view函数创建,一直维持到事务结束,这样就实现了repeatable read隔离级别。

正是因为6中的read view 生成原则,导致在不同隔离级别()下,read committed 总是读最新一份快照数据,而repeatable read 读事务开始时的行数据版本。

4.InnoDB MVCC 实现原理的深刻反思


上述更新前建立undo log,根据各种策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,这个可能与我们所理解的MVCC有较大的出入。

一般我们认为MVCC有下面几个特点:

每行数据都存在一个版本,每次数据更新时都更新该版本
修改时Copy出当前版本随意修改,个事务之间无干扰
保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)
就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道。。。,而

Innodb的实现方式是:

事务以排他锁的形式修改原始数据
把修改前的数据存放于undo log,通过回滚指针与主数据关联
修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)

二者最本质的区别是,当修改数据时是否要排他锁定,如果锁定了还算不算是MVCC?

Innodb的实现真算不上MVCC,因为并没有实现核心的多版本共存,undo
log中的内容只是串行化的结果,记录了多个事务的过程,不属于多版本共存。但理想的MVCC是难以实现的,当事务仅修改一行记录使用理想的MVCC模式
是没有问题的,可以通过比较版本号进行回滚;但当事务影响到多行数据时,理想的MVCC据无能为力了。

比如,如果Transaciton1执行理想的MVCC,修改Row1成功,而修改Row2失败,此时需要回滚Row1,但因为Row1没有被
锁定,其数据可能又被Transaction2所修改,如果此时回滚Row1的内容,则会破坏Transaction2的修改结果,导致
Transaction2违反ACID。

理想MVCC难以实现的根本原因在于企图通过乐观锁代替二段提交。修改两行数据,但为了保证其一致性,与修改两个分布式系统中的数据并无区别,
而二提交是目前这种场景保证一致性的唯一手段。二段提交的本质是锁定,乐观锁的本质是消除锁定,二者矛盾,故理想的MVCC难以真正在实际中被应
用,Innodb只是借了MVCC这个名字,提供了读的非阻塞而已。

5.福利彩蛋

职位:腾讯OMG 广告后台高级开发工程师;
Base:深圳;
场景:海量数据,To B,To C,场景极具挑战性。
基础要求:
熟悉常用数据结构与算法;
熟悉常用网络协议,熟悉网络编程;
熟悉操作系统,有线上排查问题经验;
熟悉MySQL,oracle;
熟悉JAVA,GoLang,c++其中一种语言均可;
可内推,欢迎各位优秀开发道友私信[微笑]
期待关注我的开发小哥哥,小姐姐们私信我,机会很好,平台对标抖音,广告生态平台,类似Facebook 广告平台,希望你们用简历砸我~
联系方式 微信 13609184526

博客搬家:大坤的个人博客
欢迎评论哦~

数据库事务特征、数据库隔离级别,各级别数据库加锁情况(含实操)--read committed MVCC...相关推荐

  1. 数据库事务4种隔离级别和7种传播行为

    一.数据库隔离级别:是在在数据库操作中,为了有效保证并发读取数据的正确性提出的. 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大.对于多数应用程序,可以优先考虑把数据库系统的隔 ...

  2. oracle集群数据库事务配置,在 .NET中使用Oracle数据库事务-安装配置-Oracle频道-中国IT实验室...

    在本文中,您将了解到如何在 Visual Basic .NET (VB.NET) 和 Visual .NET () 中使用数据库事务.具体来讲,您将系统学习数据库事务.在 .NET 程序中使用 Tra ...

  3. mysql数据 锁 隔离级别_MySQL数据库事务各隔离级别加锁情况--read uncommitted篇

    1.目的 1.1 合适人群 1.数据库事务特征我只是背过,并没有很深刻的理解. 2.数据库事务的隔离级别只是了解,并没有深刻理解,也没有在实际工作中体验使用过. 3.经常面试被人问起数据库加锁情况,一 ...

  4. mysql数据库事务有几种特性_面试官:你能说说事务的几个特性是啥?有哪几种隔离级别?...

    1.面试题事务的几个特点是什么? 数据库事务有哪些隔离级别? MySQL的默认隔离级别?2.面试官心里分析 用mysql开发的三个基本面:存储引擎.索引,然后就是事务,你必须得用事务. 因为一个业务系 ...

  5. 数据库事务的四大特性以及隔离级别

    本篇文章主要介绍数据库事务的四大特性ACID,以及数据库的隔离级别. 事务 概念 事务指的是满足 ACID 特性的一系列操作.在数据库中,可以通过 Commit提交一个事务,也可以使用 Rollbac ...

  6. mysql spring隔离级别_MySQL事务与Spring隔离级别实现

    1.事务具有ACID特性 原子性(atomicity):一个事务被事务不可分割的最小工作单元,要么全部提交,要么全部失败回滚. 一致性(consistency):数据库总是从一致性状态到另一个一致性状 ...

  7. mysql当前时间减一分钟_MySQL数据库事务的机制【总结】

    php中文网最新课程 每日17点准时技术干货分享 这几天面试多次被问到了数据库事务机制.隔离级别.乐观锁悲观锁类的问题,之前对这些只能说有所了解,有些概念还停留在记忆层面,没有理解,所以回答的不好.后 ...

  8. 技术复习-数据锁和数据库事务

    2019独角兽企业重金招聘Python工程师标准>>> 一.数据库锁 1.读写锁 排它锁 简称X锁(Exclusive) 一个事务对数据对象(库.表.页.行)A加X锁,可以对A进行读 ...

  9. 数据库事务原理及并发、死锁

    1. 什么是数据库事务 1.1 数据库事务是指作为单个逻辑工作单元执行的一系列操作(SQL语句).这些操作要么全部执行,要么全部不执行. 1.2 通过ACID实现数据库事务模型 1.2.1 原子性(A ...

最新文章

  1. Left,Right,Outer和Inner Joins有什么区别?
  2. 由IP和掩码计算广播地址
  3. AppBoxFuture(四). 随需而变-Online Schema Change
  4. AppiumDriver java部分api
  5. 【转载】关于测试人员的职业发展
  6. Java注释:探究和解释
  7. oracle数据库中函数和存储过程中的区别
  8. oracle创建多个游标,Oracle——游标的创建和使用
  9. 【转】winIO的解释与使用
  10. 【转载】Tomcat内存溢出的原因及调试
  11. 在ashx文件中使用Session
  12. 如何正确的获得一个view的宽和高
  13. 2021年全国职业院校技能大赛(中职组)网络安全竞赛试题(3)(总分100分)
  14. 一个老程序员在情人节对中国软件业的致言!(转)
  15. 任意阶幻方的解法及c++实现
  16. Definer 预言机攻击事件分析
  17. Dreamweaver CS6下载及安装步骤
  18. 实现微信小程序web-view内嵌H5中的下载功能(大文件切片下载)
  19. 微信如何取消企业微信消息通知 ?
  20. ccproxy的缺省代理端口列表

热门文章

  1. PMSM矢量控制笔记(1.2)——隐机电机与凸极电机的详细区别
  2. CSP-S2022 游记
  3. 嵌入式学习笔记--jlink 工具RTT使用笔记
  4. 今天给大家介绍一下苹果XR是多大尺寸的
  5. 松勤11期软件测试之Jmeter高级性能测试项目实战学习笔记
  6. 关于股市的各种小段子
  7. HTML中chunked解码和gzip解压
  8. Xposed模块之抢红包
  9. JavaWeb 文件上传和下载
  10. python:随机生成加法题目,输入答案进行判断【进阶:加减乘除】