前言

关于事务,是一个很重要的知识点,大家在面试中也会被经常问到这个问题;

数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,**锁的应用最终导致不同事务的隔离级别 **;在上一篇文章中我们说到了数据库锁的一部分知识,知道了InnoDB是支持行锁的,但是走行锁是基于索引的;

这里我们会说一下和锁紧密相关的事务;

希望本文对大家有所帮助;

引入

本文参考文章:数据库的两大神器

事务和MVCC

数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,**锁的应用最终导致不同事务的隔离级别 **;

关于事务,大家也是比较熟悉的,在这里我们再来唠叨一下:

说到事务,就不得不提它的特性以及隔离级别了;

特性

事务具有四个特性:原子性、一致性、隔离性、持久性。这四个属性通常被称为ACID属性。

  • 原子性(Atomicity) :事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。

对于以上的四个特性,我们来拿经典的转账的例子来说明;

有A和B两个人,现在A需要往B的账户上转钱,一般的操作是这样:

  1. A账户需要读取账户余额(500);
  2. A需要给B转账100元,所以需要从A的账户上扣除100元(500 - 100);
  3. 把减去的结果写回A账户(400);
  4. B账户需要读取账户余额(500);
  5. 对B账户进行加的操作(500 + 100);
  6. 把结果写回B账户(600);

以上是转账的操作步骤,我们来说明一下事务的四大特性:

原子性

以上的六步操作要么全部执行,要么全部不执行。不管执行到那一步出现了问题,就需要执行回滚操作;

一致性

在转账之前,A和B的账户加一起500 + 500 = 1000 元,在转账之后A和B的账户加起来是400 + 600 = 1000。也就是说,数据的状态在执行该事务操作之后从一个状态改变到了另外一个状态,需要保持一致性;

隔离性

在A向B转账的过程中,只要所处事务还没有提交,其他事务查询A或者B账户的时候,两个账户的金额都不会发生变化;

如果在A给B转账的同时,有另外一个事务执行了C给B转账的操作,那么当两个事务都结束的时候,B账户里面的钱应该是A转给B的钱加上C转给B的钱再加上自己原有的钱;

持久性

一旦转账成功,事务提交,所做的修改就会永久的保存;

参考文章:https://www.hollischuang.com/archives/898

隔离级别

我们对于事务的隔离级别也是很清楚的,分为四种:

  • Read uncommitted:未提交读
  • 最低级别,会出现脏读、不可重复读、幻读。
  • Read committed:已提交读
  • 避免脏读,会出现不可重复读和幻读。
  • Repeatable read:可重复读
  • 避免脏读和不可重复读,会出现幻读(在MySQL实现的Repeatable read配合gap锁不会出现幻读!)。
  • Serializable :串行化
  • 避免脏读、不可重复读、幻读。

脏读

在Read uncommitted隔离级别下会出现脏读,我们先来看一下脏读;

脏读:一个事务读取到另一个事务未提交的数据的情况被称为脏读。

举例说明:

还是拿转账的例子作为说明。A向B转账,A执行了转账语句,但A还没有提交事务,B读取数据,发现自己账户钱变多了!B跟A说,我已经收到钱了。A回滚事务【rollback】,等B再查看账户的钱时,发现钱并没有多。

分析:

出现脏读的本质就是因为操作(修改)完该数据就立马释放掉锁,导致读的数据就变成了无用的或者是错误的数据

解决(Read committed):

从上面的分析也能看出来,解决的方式就是把锁释放的位置放到事务提交之后 。这样的话,在事务还未提交之前,其他的事务对该数据是无法进行操作的,这也是Read committed避免脏读的做法;

不可重复读

Read committed 虽然避免了脏读但是会出现不可重复读;

不可重复读:一个事务读取到另外一个事务已经提交的数据,也就是说一个事务可以看到其他事务所做的修改 ;

举例说明:

事务A在读取一条数据,得到结果a,事务B把这条数据改成了b并提交了事务,这个时候事务A再次去读取这条数据,得到的结果是b。这样就发生了不可重复读;

分析:

Read committed 采用的是语句级别的快照!每次读取的都是当前最新的版本

解决:

Repeatable read避免不可重复读是事务级别的快照!每次读取的都是当前事务的版本,即使被修改了,也只会读取当前事务版本的数据。

这里涉及到了快照一词,我们需要说一下这个东西:

MVCC

MVCC(Multi-Version Concurrency Control):多版本并发控制 。通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本。一句话总结就是 同一份数据临时保留多版本的一种方式,进而实现并发控制

快照有两个级别

  • 语句级
  • 针对于Read committed隔离级别
  • 事务级别
  • 针对于Repeatable read隔离级别

InnoDB MVCC实现分析

InnoDB 的 MVCC, 是通过在每行记录后面保存两个隐藏的列来实现的, 这两个列,分别保存了这个行的创建时间,一个保存的是行的删除时间。这里存储的并不是实际的时间值, 而是系统版本号 (可以理解为事务的 ID),每次开始一个新的事务,系统版本号就会自动递增当删除一条数据的时候,该数据的删除时间列就会存上当前事务的版本号 ;事务开始时刻的系统版本号会作为事务的 ID;

下面看一下在 REPEATABLE READ 隔离级别下, MVCC 具体是如何操作的;

例子

首先创建一个表:

1CREATE TABLE `mvcc` (
2  `id` bigint(20) NOT NULL AUTO_INCREMENT,
3  `username` varchar(255) NOT NULL,
4  PRIMARY KEY (`id`)
5) ENGINE=InnoDB CHARSET=utf8;

假设系统版本号从1开始;

INSERT

InnoDB 为新插入的每一行保存当前系统版本号作为版本号,上面我们假设系统版本号从1开始;

1start transaction;
2INSERT INTO `mvcc`(username) VALUES ('tom'),('joey'),('James');
3commit;

得到如下结果(后面两列是隐藏的,通过查询语句看不到):

id username 创建时间 (事务 ID) 删除时间 (事务 ID)
1 tom 1 undefined
2 joey 1 undefined
3 james 1 undefined
SELECT

InnoDB会根据以下两个条件检查每条记录:

  • InnoDB只会查找版本早于当前事务版本的数据行(创建时间系统版本号小于或等于当前事务版本号),这样可以确保事务读取到的数据要么是本次事务开始之前就已经存在的,要么是当前事务本身做的修改;
  • 行的删除版本要么是未定义,要么大于当前事务的版本号,这样确保了事务读取到的行,在事务开始之前未被删除;

以上两个条件同时满足的情况下,才能作为结果返回;

DELETE

InnoDB 会为删除的每一行保存当前系统的版本号 (事务的 ID) 作为删除标识;

具体例子:

第二个事务,系统版本号为2;

1start transaction;
2select * from mvcc; //step 1
3select * from mvcc; //step 2
4commit;

情况一

第三个事务,系统版本号为3;

1start transaction;
2INSERT INTO `mvcc`(username) VALUES ('yang');
3commit;

当我们执行step 1刚完毕,这个时候第三个事务往表中插入了一条数据,这个时候表中的数据如下:

id username 创建时间 (事务 ID) 删除时间 (事务 ID)
1 tom 1 undefined
2 joey 1 undefined
3 james 1 undefined
4 yang 3 undefined

然后step 2执行了,得到如下结果:

id username 创建时间 (事务 ID) 删除时间 (事务 ID)
1 tom 1 undefined
2 joey 1 undefined
3 james 1 undefined

大家可能会感到迷惑,第三个事务不是往里面插入了一条数据吗,怎么查不到。这个时候我们来说一下原因:

  • id = 4是由事务三(系统版本为3)创建的,该数据的创建时间(事务ID)为3;
  • 第二个事务的系统版本号是2,大家要记得我们上面说的查询的两个条件;
  • InnoDB只会查找创建时间(事务ID)小于或等于当前事务的数据行;
  • 查找删除时间(事务ID)列大于当前系统版本号的数据行;
  • id = 4的数据的创建时间(事务ID)明显大于第二个事务的系统版本号,而且删除时间也是未定义的,所以第三个事务插入的数据未被检索;

情况二

第四个事务,系统版本为4:

1start transaction;  
2delete from mvcc where id=1;
3commit;  

当第二个事务执行了step 1,这个时候第三个事务的插入也执行完毕了,接着事务四开始执行,此时数据库的数据如下:

id username 创建时间 (事务 ID) 删除时间 (事务 ID)
1 tom 1 4
2 joey 1 undefined
3 james 1 undefined
4 yang 3 undefined

上面可以看出,当执行DELETE操作的时候,删除时间(事务ID)列会存上当前事务的系统版本号;

然后step 2执行了,得到如下结果:

id username 创建时间 (事务 ID) 删除时间 (事务 ID)
1 tom 1 4
2 joey 1 undefined
3 james 1 undefined

具体原因我就不说了(SELECT查询的两个条件);

UPDATE

InnoDB 执行 UPDATE,实际上是新插入的一行数据 ,并保存其创建时间(事务ID)为当前事务的系统版本号,同时保存当前事务系统版本号到需要UPDATE的行的删除时间(事务ID)

情况三

第五个事务,系统版本号为5:

1start transaction;
2update mvcc set name='jack' where id = 3;
3commit;

当执行完step 1,第三个的插入和第四个事务的删除都执行完毕并且提交,又有一个用户执行了第五个事务的更新操作,这个时候,数据库数据如下:

id username 创建时间 (事务 ID) 删除时间 (事务 ID)
1 tom 1 4
2 joey 1 undefined
3 james 1 5
4 yang 3 undefined
3 jack 5 undefined

然后我们执行step 2得到如下数据:

id username 创建时间 (事务 ID) 删除时间 (事务 ID)
1 tom 1 4
2 joey 1 undefined
3 james 1 5

以上几种情况可以看出,不管咋样,查出的数据都是和第一次查询的数据一致,尽管其他事务做了各种修改操作,但是没有影响到第二个事务中的查询操作;

通过以上对MVCC的介绍,我想大家也明白了Repeatable read避免不可重复读的方式;

参考文章:https://blog.csdn.net/whoamiyang/article/details/51901888

幻读

幻读:是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致 (幻读是事务非独立执行时发生的一种现象);

举例说明:

例如事务A对一个表中符合条件的一些数据做了从a修改为b的操作,这时事务B又对这个表中插入了符合A修改条件的一行数据项,而这个数据项的数值还是为a并且提交给数据库。而操作事务A的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务B中添加的,就好像产生幻觉一样,这就是发生了幻读。

解决:

但在MySQL实现的Repeatable read配合间隙锁不会出现幻读;

使用间隙锁锁住符合条件的部分,不允许插入符合条件的数据。

间隙锁

间隙锁:当我们用范围条件检索数据而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合范围条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”。InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(间隙锁只会在Repeatable read隔离级别下使用)。

InnoDB使用间隙锁的目的有两个:

  • 为了防止幻读
  • 满足恢复和复制的需要
  • MySQL的恢复机制要求:在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录,也就是不允许出现幻读

总结

本文介绍了MySQL数据锁以及事务的一些知识点,下面我们来总结一下;

事务的四大特性:

  • 原子性(Atomicity :事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。

对于事务的隔离级别也是很清楚的,分为四种:

  • Read uncommitted:未提交读
  • 最低级别,会出现脏读、不可重复读、幻读。
  • Read committed:已提交读
  • 避免脏读,会出现不可重复读和幻读。
  • Repeatable read:可重复读
  • 避免脏读和不可重复读,会出现幻读(在MySQL实现的Repeatable read配合gap锁不会出现幻读!)。
  • Serializable :串行化
  • 避免脏读、不可重复读、幻读。

MVCC(Multi-Version Concurrency Control):多版本并发控制 ,一句话总结就是 同一份数据临时保留多版本的一种方式,进而实现并发控制 (上面也简单的演示了InnoDB MVCC的实现);

MVCC能够实现读写不阻塞

快照有两个级别

  • 语句级
  • 针对于Read committed隔离级别
  • 事务级别
  • 针对于Repeatable read隔离级别

Repeatable read避免不可重复读是事务级别的快照!每次读取的都是当前事务的版本,即使被修改了,也只会读取当前事务版本的数据。

最后

本文简单的说了一下事务一块的东西,有问题的话还望大家指教,本人一定抱着虚心学习的态度。

大家共同学习,一起进步!

事务与MVCC的更多相关文章

  1. HBase的写事务,MVCC及新的写线程模型

    MVCC是实现高性能数据库的关键技术,主要为了读不影响写.几乎所有数据库系统都用这技术,比如Spanner,看这里.Percolator,看这里.当然还有mysql.本文说HBase的MVCC和0.9 ...

  2. MySQL中的事务和MVCC

    本篇博客参考掘金小册--MySQL 是怎样运行的:从根儿上理解 MySQL 以及极客时间--MySQL实战45讲. 虽然我们不是DBA,可能对数据库没那么了解,但是对于数据库中的索引.事务.锁,我们还 ...

  3. 面试官:什么是MySQL 事务与 MVCC 原理?

    作者:小林coding 图解计算机基础网站:https://xiaolincoding.com/ 大家好,我是小林. 之前写过一篇 MySQL 的 MVCC 的工作原理,最近有读者在网站上学习的时候, ...

  4. MySQL 各级别事务的实现机制

    MySQL 各级别事务的实现机制在处理cnctp项目已合包裹状态同步的问题时,发现读包裹状态和对包裹状态的更新不在一个事务内,我提出是否会因为消息并发导致状态一致性问题.在和同事讨论的过程中,我们开始 ...

  5. Mysql 事务及其原理

    Mysql 事务及其原理 什么是事务 什么是事务?事务是作为单个逻辑工作单元执行的一系列操作,通俗易懂的说就是一组原子性的 SQL 查询.Mysql 中事务的支持在存储引擎层,MyISAM 存储引擎不 ...

  6. MongoDB新存储引擎WiredTiger实现(事务篇)

    导语:计算机硬件在飞速发展,数据规模在急速膨胀,但是数据库仍然使用是十年以前的架构体系,WiredTiger 尝试打破这一切,充分利用多核与大内存时代,开发一种真正满足未来大数据管理所需的数据库.本文 ...

  7. 面试中的老大难-mysql事务和锁,一次性讲清楚!

    众所周知,事务和锁是mysql中非常重要功能,同时也是面试的重点和难点.本文会详细介绍事务和锁的相关概念及其实现原理,相信大家看完之后,一定会对事务和锁有更加深入的理解. 本文主要内容是根据掘金小册& ...

  8. MySQL MVCC原理深入探索

    一.MVCC的由来 二.MVCC的实际应用 RR级别场景 RC级别场景 三.MVCC的实现 3.1 多版本的数据从哪里来--Undo Log 3.1.1 插入操作对应的undo log 3.1.2 删 ...

  9. 理解 MVCC

    MongoDB.MySQL.Oracle.PostgreSQL 等事务型数据库都有 mvcc 的概念. MVCC: 即多版本并发控制,主要是为了提高数据库的读写性能,让数据库在读写的时候不用去加锁.m ...

随机推荐

  1. Azure Linux 虚拟机常见导致无法远程的操作

    对Azure虚拟机的一些操作可能会导致无法远程连接,本文罗列了以下导致不能远程连接的场景: 场景1 - 在虚拟机配置IP地址或MAC地址 场景2 - 错误地修改服务的配置文件 场景3 - 误设置防火墙 ...

  2. C#耗时计算

    System.Diagnostics.Stopwatch watch = new Stopwatch(); watch.Start(); //init();计算耗时的方法 watch.Stop(); ...

  3. Python学习---IO模型1227

    1.1. 事件驱动 事件驱动属于一种编程的范式,一种编程的风格,它擅长于处理一些未知的事件,通过绑定一个事件,外界触发后激活这个事情,达到执行某些操作的目的.比如浏览器的onclick()事件 1.2 ...

  4. Python实例---FTP小程序

    [更多参考] 点击下载

  5. 5 Dockerfile指令详解 && CMD 指令

    CMD 指令的格式和 RUN 相似,也是两种格式: shell 格式: CMD <命令> exec 格式: CMD ["可执行文件", "参数1", ...

  6. 乘风破浪:LeetCode真题_005_Longest Palindromic Substring

    乘风破浪:LeetCode真题_005_Longest Palindromic Substring 一.前言 前面我们已经提到过了一些解题方法,比如递推,逻辑推理,递归等等,其实这些都可以用到动态规划 ...

  7. TensorFlow神经网络中的激活函数

    激活函数是人工神经网络的一个极其重要的特征.它决定一个神经元是否应该被激活,激活代表神经元接收的信息与给定的信息有关. 激活函数对输入信息进行非线性变换. 然后将变换后的输出信息作为输入信息传给下一层 ...

  8. codeforces 932E Team Work(组合数学、dp)

    codeforces 932E Team Work 题意 给定 \(n(1e9)\).\(k(5000)\).求 \(\Sigma_{x=1}^{n}C_n^xx^k\). 题解 解法一 官方题解 的 ...

  9. scala当中的Actor并发编程

    注:Scala Actor是scala 2.10.x版本及以前版本的Actor. Scala在2.11.x版本中将Akka加入其中,作为其默认的Actor,老版本的Actor已经废弃. 1.什么是Sc ...

  10. 关闭window端口445

    首先,来查看下系统当前都开放了什么端口,怎样查看呢?调出cmd命令行程序,输入命令”netstat -na“,可以看到. 接着,可以发现当前系统开放了135.445以及5357端口,而且从状态看都处于 ...