数据库死锁&数据库锁知识点
参考:https://www.cnblogs.com/yelongsan/p/9405914.html
参考带图:https://www.wengbi.com/thread_94416_1.html
技术内幕四维图:[url]https://blog.csdn.net/tanliqing2010/article/details/81509539[/url]
什么是数据库死锁:
1.阻塞现象
程序在执行的过程中,点击确定或保存按钮,程序没有响应,也没有出现报错。
2.阻塞原理:
当对于数据库某个表的某一列做更新或删除等操作,执行完毕后该条语句不提
交,另一条对于这一列数据做更新操作的语句在执行的时候就会处于等待状态,
此时的现象是这条语句一直在执行,但一直没有执行成功,也没有报错。
3.死锁现象:
第一个连接占有资源没有释放,准备获取第二个连接所占用的资源,而第二个连接占有资源没有释放,准备获取第一个连接所占用的资源。
这种互相占有对方需要获取的资源的现象叫做死锁。
对于死锁,数据库处理方法:牺牲一个连接,保证另外一个连接成功执行。
4.死锁原理:
两个进程并发处理业务AB,业务A先后处理表a表b,业务B先后处理表b表a,业务A锁定等待提交a,业务B锁定等待提交b,此时业务A继续抢占表b,业务B继续抢占表a,但表a表b彼此被另一方锁定,这种相互占有对方需要的资源是数据库死锁。
此锁可对表所有数据行加锁,也可对表某一行加锁。
5.根据死锁所需要的4大条件,提出解决方案:
1)互斥条件:
指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
进程A处理表a表b,进程B等待进程A处理完成。
2)请求和保持条件:
指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:
指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:
指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
以固定的顺序访问表和行
6.下列方法有助于最大限度地降低死锁:
1)以固定的顺序访问表和行。即按顺序申请锁,这样就不会造成互相等待的场面。
进程A处理表a表b,进程B也处理表a表b。
2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。
进程A处理表a表b事务拆开,进程B也处理表a表b事务拆开。
3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。
进程A处理表a表b,同时锁定表a表b,不被进程B占用。
4)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR(repeatableread)调整为RC(readcommit),可以避免掉很多因为gap(间隙锁)锁造成的死锁。
进程A和进程B,是用隔离级别低的事务,要对spring的隔离级别由了解
比如使用UNCOMMITTED,允许读取尚未提交的更改。可能导致脏读、幻影读或不可重复读。
5)为表添加合理的索引。如果不走索引将会为表的每一行记录添加上锁(表锁),死锁的概率大大增大。
意思是如果走索引,记录只会加行锁,减少死锁的概率。
7.此问题会延伸到数据库索引和锁知识点,每个知识点看似简单,但让你说说看,估计就GG了。
推荐https://www.cnblogs.com/yelongsan/p/9405914.html
1).InnoDB行锁和表锁都支持!
MyISAM只支持表锁!
2).InnoDB只有通过索引条件检索数据才使用行级锁,否则,InnoDB将使用表锁
也就是说,InnoDB的行锁是基于索引的!
3).InnoDB和MyISAM有两个本质的区别:
InnoDB支持行锁
InnoDB支持事务
4).共享锁(S锁)/读锁:允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
也叫做读锁:读锁是共享的,多个客户可以同时读取同一个资源,但不允许其他客户修改。
共享锁--读锁--S锁:SELECT*FROMtable_nameWHERE...LOCKINSHAREMODE。
排它锁--写锁--X锁:SSELECT*FROMtable_nameWHERE...FORUPDATE。
排他锁(X锁)/写锁:允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
也叫做写锁:写锁是排他的,写锁会阻塞其他的写锁和读锁。
用写锁锁表,会阻塞其他事务读和写
对于UPDATE、DELETE、INSERT语句,InnoDB会自动给涉及数据集加排他锁(X)
5)意向锁:
innodb中有行锁和表锁。
正常情况下,加了行锁,表锁需要查询每一行是否加了行锁,才进行加表锁成功。
这里,innodb有了意向锁的含义,在加行锁的同时数据库自动加了表的意向锁,不必再去查每一行是否加行锁,表锁只需要查询有没有意向锁就好。
https://www.zhihu.com/question/51513268
行锁->表锁,表锁不需要再每一行去检查有没有行锁,直接有没有之前加过的意向锁,有了阻塞等待,没有加表锁。注意在第一步加行锁时,是数据库自动完成申请一向锁,意向锁不需要我们自行添加
为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(IntentionLocks),这两种意向锁都是表锁:
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁,共享锁被阻塞。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁,排他锁被阻塞。
step1:判断表是否已被其他事务用表锁锁表
step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。
6)数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,锁的应用最终导致不同事务的隔离级别
首先我们回忆下spring隔离级别:
隔离级别:
ISOLATION_DEFAULT使用后端数据库默认的隔离级别。
readuncommitted:
ISOLATION_READ_UNCOMMITTED允许读取尚未提交的更改。可能导致脏读、幻影读或不可重复读。
隔离级别最低,并发性能高,没有排它锁,因为可以更改所以也没有共享锁。
readcommitted:
ISOLATION_READ_COMMITTED允许从已经提交的并发事务读取。可防止脏读,但幻影读和不可重复读仍可能会发生。
把释放锁的位置调整到事务提交之后,此时在事务提交前,其他进程是无法对该行数据进行读取的,包括任何操作
加了排他锁,锁定正在读取的行
repeatableread:
ISOLATION_REPEATABLE_READ可保证对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。
可防止脏读和不可重复读,但幻影读仍可能发生。
锁定已读取的所有行
幻读:在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。使用gap间隙锁,把其他空业务数据也给锁定,可防止幻读。
serlializable:
ISOLATION_SERIALIZABLE完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。
这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。
完全隔离
7)乐观锁:updateAsetName=lisi,version=version+1whereID=#{id}andversion=#{version}
悲观锁:select*fromxxxxforupdate手动加行锁排他锁
8)间隙锁GAP,只会在可重入读的隔离级别使用,比如>100,即便数据库没有102,也会把id=102加行锁。
9)MVCC(Multi-VersionConcurrencyControl)多版本并发控制,
可以简单地认为:MVCC就是行级锁的一个变种(升级版)。
MVCC一般读写是不阻塞的(所以说MVCC很多情况下避免了加锁的操作)
读写不阻塞:多版本并发控制--->通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本。
语句级锁
针对于Readcommitted隔离级别,每次都是读语句级别数据快照(Snapshot),每次读取的都是当前最新的版本。
事务级别锁
针对于Repeatableread隔离级别,避免不去重复读是事务级别的快照!
可以从回答read_commmit,repeatableread实现原理得到答案:
read_commmit语句级快照,每次读取最新版本的快照;
repeatableread事务级别的快照,表里每个数据行都隐式有版本和过期时间的字段,一个事务来处理,会获取小于等于当前版本号的数据(数据快照)
总结:
InnoDB基于行锁还实现了MVCC多版本并发控制,MVCC在隔离级别下的Readcommitted和Repeatableread下工作。MVCC能够实现读写不阻塞!
InnoDB实现的Repeatableread隔离级别配合GAP间隙锁已经避免了幻读!
相关推荐
最近,公司现网的业务中出现上图所示的死锁异常,沿着问题分析,发现这个问题涉及很多数据库的基础知识。 <update id = "A" parameterType = "java.util.List"&