事务与MVCC

前言

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

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

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

但愿本文对你们有所帮助;数据库

引入

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

事务和MVCC

数据库事务有不一样的隔离级别,不一样的隔离级别对锁的使用是不一样的,锁的应用最终致使不一样事务的隔离级别mvc

关于事务,你们也是比较熟悉的,在这里咱们再来唠叨一下:post

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

特性

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

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

对于以上的四个特性,咱们来拿经典的转帐的例子来讲明;.net

有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的钱再加上本身原有的钱;

持久性

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

参考文章:www.hollischuang.com/archives/89…

隔离级别

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

  • 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 具体是如何操做的;

例子

首先建立一个表:

CREATE TABLE `mvcc` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB CHARSET=utf8;
复制代码

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

INSERT

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

start transaction;
INSERT INTO `mvcc`(username) VALUES ('tom'),('joey'),('James');
commit;
复制代码

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

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

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

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

以上两个条件同时知足的状况下,才能做为结果返回;

DELETE

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

具体例子:

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

start transaction;
select * from mvcc; //step 1
select * from mvcc; //step 2
commit;
复制代码

状况一

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

start transaction;
INSERT INTO `mvcc`(username) VALUES ('yang');
commit;
复制代码

当咱们执行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:

start transaction;  
delete from mvcc where id=1;
commit;  
复制代码

当第二个事务执行了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:

start transaction;
update mvcc set name='jack' where id = 3;
commit;
复制代码

当执行完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避免不可重复读的方式;

参考文章:blog.csdn.net/whoamiyang/…

幻读

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

举例说明:

例如事务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避免不可重复读是事务级别的快照!每次读取的都是当前事务的版本,即便被修改了,也只会读取当前事务版本的数据。

最后

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

你们共同窗习,一块儿进步!

相关文章
相关标签/搜索