如何合理使用数据库锁

 

一、前言

死锁,在我们的项目中有发生过,但不频繁;但是因为锁处理的不好,不能合理地规划锁,导致性能下降是经常发生的。通过本文章,除了了解如何避免死锁外,更多的内容是如何使用锁。本文不会讲的很细,有的地方也不是很严谨,但是不影响对内容的理解。更细节和高级的知识,可以百度。

 

二、什么是锁

我们这里讲的锁,是数据库的锁(lock)。当数据库要对某个表,或者某条数据修改时,会首先将其加锁,防止其他进程或事务修改它,以免产生不可预期的结果。关于锁的定义,锁的类型,大家可以搜索。锁的内容(或者称为对象,比如表,表的记录),一般称之为资源。

锁一般有两个级别:行级锁和表级锁。前者的锁定范围是表记录,后者的范围是整表。显然,后者的范围更大。

 

三、何时会发生“锁”

当执行update语句时,数据库会对修改范围内的表记录加锁;

当你修改表的结构时,比如 alter table…,数据库会对表加锁

当你执行select 1 from bsuser for update 时,会对整表加锁。这种语法俗称“显式加锁”。

锁的有效期会从加锁开始直到事务结束(提交或者回滚)。

我不知道有没有主动释放锁的语法,感兴趣的可以查一查。

 

四、什么是死锁

锁不可怕,在系统运行期间,数据库不停地产生锁,释放锁,是很平常的事情,但是可怕的是死锁(dead lock),发生死锁,会导致多个进程阻塞等待。这时候,数据库会提示“系统在申请资源时,检测到死锁”类似的提示信息。死锁就像十字路口塞车一样,四个方向都走不了。

死锁发生有四个必要条件,我只讲最后一个,也是最关键的,就是两个事务互相申请被对方持有的资源。举例:进程(事务)A持有资源R1,进程(事务)B持有资源R2,现在进程A申请R2,进程B申请R1。就是两个进程互相申请被对方加锁(持有)的资源,且互不相让,死锁发生了。

发生死锁后,数据库会在等待一段时间后,根据一定的原则,牺牲掉一个进程,以避免死锁持续下去。

 

五、锁的危害

数据库锁是保证数据数据正确被修改的必不可少的手段,是关系型数据库很重要的一个保证数据完整性的工具。这里不说锁的优点,单说其缺点,然后研究怎么规避这些缺点,降低锁的负面影响。

1、      降低系统性能。一般资源被进程锁定,其他要申请该资源的进程就要等待,直到资源被前一个进程释放,这个等待过程就是系统性能降低的过程。等待的越久,系统性能下降的约厉害。

2、      导致死锁。死锁会导致更长时间的进程等待,而且这种等待是无解的,只有通过外部力量的干涉才能解锁,比如数据库介入,牺牲掉一个进程。这样这个进程的操作就会被回滚。

锁等待或者死锁对用户的直接影响是,如果用户的一个界面操作导致的数据库操作被锁阻塞,该处理进程处于等待状态,那用户的操作就会被冻结,不会得到响应。用户的话说就是“界面卡了”。这种情况如果多了,卡的时间长了,会导致用户的体验急剧下降,如果出现死锁,还会导致用户所做的修改不能更新到数据库中,被回滚。

 

六、如何降低锁的影响,避免死锁

我们在实践中,要了解锁发生的原因,锁的机制,正确编程,合理使用锁机制。

1、      避免“锁”

有时候,必须通过锁确保程序执行完美。但是凡事都要有代价。有时候我们需要仔细将加锁带来的副作用和带来的好处做个对比。如果带来的坏处远远大于带来的好处,或者换句话说,如果带来的好处非常小,我们就可以不使用锁。我举个例子。在对考核计划增加考核对象时,要检查不能加入重复的考核对象。为了完美实现这个业务逻辑,需要对考核计划加锁,使得在增加对象时,其他程序都不可以增加对象。这样做完美地实现了这个业务逻辑,但是由于是对整表加锁,大大增加了锁冲突的可能,而且,如果不这样做的话,根据用户的使用场景分析,考核对象重复增加的可能性也极低。这种情况下,就可以不使用锁。为了避免锁冲突(这种大的副作用),我可以容忍在某种极端情况下导致考核对象重复的情况发生(极小概率出现的副作用)。但是后面我会给出一个方案,来确保程序完美的前提下,如何缩小锁的粒度(不锁表)。

2、      尽量缩短“锁”的时间

使用锁的一个原则就是 尽可能晚地加锁,尽可能早地释放锁。也就是说,要合理规划处理逻辑,只在必须的最后时刻才执行update语句(加锁),并尽可能早提交或回滚事务(释放锁)。一个错误的例子就是:

   1update ………………………

   2)复杂的耗时的java代码

   3commit

如果可能话,就把 (1)和(2)调换下顺序,推迟加锁的时间。

缩短锁的时间,不能完全避免冲突,但是可以降低发生冲突的概率。就行在大街上走,走的快一些,尽量减少在大街上行走的时间,可以降低被熟人看见概率是一个道理。

3、      减小锁的粒度

只对必要的资源加锁,不扩大锁的范围。

select 1 from for update这种锁全表要坚决避免,如果要锁指定的行,加上where限定条件。

2一样,减小锁的粒度,也能降低发生锁冲突的概率。

4、      避免死锁。

前面说过,死锁发生的根本原因就是不合理的资源申请顺序,如果多个程序都要使用相同的某几个资源,但是申请顺序不一致,就可能导致死锁发生。因此,我们要约定一个确定资源申请顺序的一个标准,大家都来遵循它。

1)按照资源id的大小顺序(比如我要锁定多条员工记录,我按照员工号的排序从大到小锁定)

2)对有层级关系的资源,按照层级从高到低的顺序申请。比如”单据头->单据明细行”的顺序

3)按照资源名称排序从大到小进行申请。比如按照表的名称排序。

 

七、后记

在实践中,对锁的认识和管理,许多新人缺乏这方面的概念和意识,没有意识到合理使用锁的重要性。但是锁对系统的性能有很大影响。合理使用锁的三个关键,1是减少资源的占用时间,2是,减少锁的粒度 ,3,绝对要避免死锁。

原文地址:https://www.cnblogs.com/senline/p/db-resoucelock.html