事务的本质和死锁的原理・改
由于一些错误操作和被爬的原因,我重新整理了上一篇文章
https://www.cnblogs.com/klarck/p/13630990.html
前言、事务方块
在我的理解,事务是这个形状的
x轴是可以锁定的资源,y轴是函数执行时间,
事务随着时间的流逝向下降落,
当恰好与x轴接触时资源被锁定,当恰好不再与x轴接触时资源解除锁定,从恰好与x轴接触到恰好不再与x轴接触这个时间段就是事务的执行时间。
这里的耗时五秒是夸张的写法,通常情况下单位是毫秒级甚至微妙级,这里使用秒作为单位是为了凸显现象
上图是函数a的事务形状,命名【 事务方块】,
@Transactional
public void a(){
//对A表修改,耗时5秒
}
一、多事务
当一个函数调用多个子函数且都是被事务注解的函数时情况是
上图是对于函数abc调用时,a函数、b函数、c函数的事务形状
public void abc(){
this.a(){}
this.b(){}
this.c(){}
}
@Transactional
public void a(){
//对A表修改,耗时五秒
}
@Transactional
public void b(){
//对B表修改,耗时五秒
}
@Transactional
public void c(){
//对C表修改,耗时五秒
}
当abc函数开始执行时,先调用a函数,先锁定a表,
当a函数结束执行,a表解锁,同时b函数开始执行,b表锁定,
当b函数结束执行,b表解锁,同时c函数开始执行,c表锁定。
相当于下图的三个事务方块联合且相对位置固定一起下落,总耗时十五秒
如果并发请求两次abc函数,事务方块如图
首先a表被请求一锁定五秒后解锁,再马上被请求二锁定五秒,
然后b表也被请求一锁定五秒后解锁,再马上被请求二锁定五秒,
最后c表也被请求一锁定五秒后解锁,再马上被请求二锁定五秒,
而请求一在解锁表a后紧接着又锁定了表b五秒,同时表a再被请求二锁定五秒
依此类推,请求二都在请求一解锁对应的表后锁定该表,
那么总运行时间二十秒
二、大事务
@Transactional
function abc(){
this.a(){}
this.b(){}
this.c(){}
}
public void a(){
//对A表修改,耗时五秒
}
public void b(){
//对B表修改,耗时五秒
}
public void c(){
//对C表修改,耗时五秒
}
其中abc函数有注解,而子函数a、b、c没有注解,事务形状如图
a、b、c三个颜色的方块是结合在一起的,必须一起执行,实际上就是事务原子性的表象,那么a表被锁定十五秒,b表被锁定十秒,c表被锁定五秒
若也同时并发两次abc函数则总耗时三十秒,容易发现事务越大,耗时越长;本来在多事务时被锁定五秒的a表,被锁定了十五秒。
在这个abc函数中,事务对表的锁定是懒锁定的情况,
就是说,当abc函数中
a函数开始执行时,只锁定了a表,当a函数执行完成,
b函数开始执行锁定b表,此时不解锁a表,当b函数执行完成,
c函数开始执行锁定c表,此时不解锁a、b两个表,当c函数执行完成,同时解锁a、b、c三个表。
这就导致了死锁的出现
三、死锁
有如下两个函数ab和ba
@Transactional
public void ab(){
this.a(){}
this.b(){}
}
@Transactional
public void ba(){
this.b(){}
this.a(){}
}
当ab和ba函数同时被执行时,事务方块类似下图,但不完全
当ab和ba函数同时被执行时,ab锁定a表,ba锁定b表,
当ab执行完a函数请求锁定b表时,同事ba也执行完了b函数请求锁定a表,
但ab没有解开对a表的锁定,ba也没有解开对b表的锁定,那么相互等待对方解锁,出现死循环,这就是死锁。
所以减少死锁出现的几率的办法是减小事务方块的大小,即减小事务方块消耗的时间或减小事务方块锁定的资源【表或行】
所以行级锁不易出现死锁,表级锁易出现死锁,是因为行级锁资源占用小,当时间相同时,行级锁的事务方块小。
当表的主键是整型的自增ID时,则相当于把事务可锁定资源从一个资源【表】分割成了2147483647份的资源【行】,可见使用行级锁时事务方块明显变小。
四、名称来历
事务方块的名字来历是一次研究死锁时联想到俄罗斯方块的情形与事务的情形类似,俄罗斯方块是为了找一个位置放置方块,而事务方块是为了找一个时空缝隙让方块执行通过,目的不同。
【网络图片侵删】