关于事务,是一个很重要的知识点,你们在面试中也会被常常问到这个问题;mysql
数据库事务有不一样的隔离级别,不一样的隔离级别对锁的使用是不一样的,锁的应用最终致使不一样事务的隔离级别;在上一篇文章中咱们说到了数据库锁的一部分知识,知道了InnoDB是支持行锁的,可是走行锁是基于索引的;面试
这里咱们会说一下和锁紧密相关的事务;sql
但愿本文对你们有所帮助;数据库
本文参考文章:数据库的两大神器并发
数据库事务有不一样的隔离级别,不一样的隔离级别对锁的使用是不一样的,锁的应用最终致使不一样事务的隔离级别;mvc
关于事务,你们也是比较熟悉的,在这里咱们再来唠叨一下:post
说到事务,就不得不提它的特性以及隔离级别了;学习
事务具备四个特性:原子性、一致性、隔离性、持久性。这四个属性一般被称为ACID属性。spa
对于以上的四个特性,咱们来拿经典的转帐的例子来讲明;.net
有A和B两我的,如今A须要往B的帐户上转钱,通常的操做是这样:
以上是转帐的操做步骤,咱们来讲明一下事务的四大特性:
以上的六步操做要么所有执行,要么所有不执行。无论执行到那一步出现了问题,就须要执行回滚操做;
在转帐以前,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隔离级别下会出现脏读,咱们先来看一下脏读;
脏读:一个事务读取到另外一个事务未提交的数据的状况被称为脏读。
举例说明:
仍是拿转帐的例子做为说明。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(Multi-Version Concurrency Control):多版本并发控制 。经过必定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供必定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库能够提供同一数据的多个版本。一句话总结就是 同一份数据临时保留多版本的一种方式,进而实现并发控制 ;
快照有两个级别:
Read committed
隔离级别Repeatable read
隔离级别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开始;
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 |
InnoDB会根据如下两个条件检查每条记录:
以上两个条件同时知足的状况下,才能做为结果返回;
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 |
你们可能会感到迷惑,第三个事务不是往里面插入了一条数据吗,怎么查不到。这个时候咱们来讲一下缘由:
状况二
第四个事务,系统版本为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查询的两个条件);
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数据锁以及事务的一些知识点,下面咱们来总结一下;
事务的四大特性:
对于事务的隔离级别也是很清楚的,分为四种:
MVCC(Multi-Version Concurrency Control):多版本并发控制 ,一句话总结就是 同一份数据临时保留多版本的一种方式,进而实现并发控制 (上面也简单的演示了InnoDB MVCC的实现);
MVCC可以实现读写不阻塞 ;
快照有两个级别:
Read committed
隔离级别Repeatable read
隔离级别Repeatable read
避免不可重复读是事务级别的快照!每次读取的都是当前事务的版本,即便被修改了,也只会读取当前事务版本的数据。
本文简单的说了一下事务一块的东西,有问题的话还望你们指教,本人必定抱着虚心学习的态度。
你们共同窗习,一块儿进步!