从字面上看,行级锁的作用范围肯定比表级锁的作用范围要小;行级锁和表级锁是根据锁的粒度来区分的,行记录,表都是资源,锁是作用在这些资源上的。如果粒度比较小(比如行级锁),可以增加系统的并发量但需要较大的系统开销,会影响到性能,出现死锁,,因为粒度小则操作的锁的数量会增加;如果作用在表上,粒度大,开销小,维护的锁少,不会出现死锁,但是并发是相当昂贵的,因为锁定了整个表就限制了其它事务对这个表中其他记录的访问。
行级锁:(tx锁,也叫事务锁)
一、悲观锁:数据库行级锁,目的是让数据被查出来的时候就加上锁,然后再执行下面的程序逻辑,这样后面为了操作相同数据而进来的请求,就会在一开始就被拦住(这种效果千万不要以为可以做防重复提交)
在操作DML(insert,update,delete)语句时,oracle会自动加上行级锁,在select * from table for update 【of column】【nowait|wait 3】时,oracle也会自动加锁
单表 for update:
1. 一般在for update 时加nowait,这样就不用等待其他事务执行了,一判断有事务,立马抛出错误。
下面简单说一下 for update的四种情况:
1.1 select * from table where id = '1001' for update 锁住了这条数据,那么另外一个人对该笔数据进行DML操作或者也执行同样的for update操作时,会检测到这笔数据上有行级锁,那么就会等待着锁释放;这样就会出现一个问题:其他的程序如果需要对这笔数据操作,就需要等,至于等多久要看锁什么时候释放!
1.2 select * from table where id = '1001' for update nowait,意思就是如果这笔数据上本身加了锁,另外一个人去执行这句SQL的时候,发现加了锁,就会直接抛出异常(ORA-00054:资源正忙),不会等待这笔数据的锁释放。
1.3 select * from table where id = '1001' for update wait 5;意思就是如果这笔数据被锁住,另外一个人如果执行这句SQL后,会等待5秒,如果5秒后这句SQL还没有得到这笔数据的锁,就会抛出异常(ORA-00054:资源正忙)
1.4 我们先执行 A语句:select * from table where id = '1001' for update 把1001加上锁,然后再执行 B语句:select * from table where id = '1001' and id ='1002' for update;这时候肯定查不出来,因为A已经把B要加锁的数据锁了,这样B联1002的数据都查不出来
解决方案:skip locked如果把B语句改为:select * from table where id = '1001' and id ='1002' for update skip locked;意思就是执行的时候如果发现要查询的数据有锁,就把加了锁的数据排除,把剩下的数据加锁,然后查出来!
上面讲到了 for update 的四种方式,实际情况如何选择呢?
关于NOWAIT(如果一定要用FOR UPDATE,我更建议加上NOWAIT) 当有LOCK冲突时会提示错误并结束STATEMENT而不是在那里等待(比如:要查的行已经被其它事务锁了,当前的锁事务与之冲突,加上nowait,当前的事务会结束会提示错误并立即结束 STATEMENT而不再等待). WAIT 子句指定等待其他用户释放锁的秒数,防止无限期的等待。
“使用FOR UPDATE WAIT”子句的优点如下:
1.防止无限期地等待被锁定的行;
2.允许应用程序中对锁的等待时间进行更多的控制。
3.对于交互式应用程序非常有用,因为这些用户不能等待不确定
4.若使用了skip locked,则可以越过锁定的行,不会报告由wait n 引发的‘资源忙’异常报告
关联表for update:
2. 现在大部分业务都是联表查询,如果用for update 的话,就会把所有关联表查询出来的列所在的行全部加锁,那这个锁可就重了,比如: select * from t1,t2 where t1.id = t2.id and t1.age = '20' for update;就会把T1和T2两个表中符合条件的行锁定;
如果上述SQL我只想对T1表的结果集加锁,怎么办?
答案:of column_name例子:
select * from t1,t2 where t1.id = t2.id and t1.age = '20' for update of t1.id;
这样就会只把T1表中的符合条件的行加锁,T2表中符合条件的行不会加锁。
PS:如果单表for update of column_name查询,其实和 for update操作是一样的!
二、乐观锁:这不是数据库本身的锁,是利用数据比较结果来当做抽象的锁;
举个例子就明白:说明:小明成绩错了,要改成绩。班主任能改,年级主任也能改!
程序:{
//先查出来小明的成绩
select t.id,t.result from T t where t.id='10001';---10001,59
//更新成绩,改为60
update T t set t.result =‘60’ where t.id='10001'and t.result = '59'
//加上这个条件的目的就是为了验证,数据库里10001的成绩在此期间有没有被其他人改过,如果改过,那就更新条数为0(因为找不到符合条件的数据);
PS:没有找到数据,所以没更新10001这笔数据,最好是程序返回一个没有更新到这笔数据的提示,如果不加任何提示,前端就会认为更新成功了!
}
分析:
1.利用数据库中的数据和已经取出的数据的一致性做为“锁”,与for update相比,乐观锁机制是等到更改数据的时候才去校验,悲观锁是读取数据就开始做了校验,
从这个角度来看,乐观锁是对数据库没有额外开销,那么效率相对是高的。
2. 需要更改的字段可以作为乐观锁的验证字段;或者表里建立version版本号,每更新一次数据版本号+1;或者加lastupdatedate(最后更新时间),同理:数据更改的同时lastupdatedate也跟着变更!
3.其实乐观锁存在一个很致命的问题:场景: 已上述小明改成绩为例,假设班主任改的同时,年级主任也改,
两个请求几乎同时执行了查询:select t.id,t.result from T t where t.id='10001';---10001,59都查出来是59分!!
然后几乎同时执行了改成绩,班主任改成60分,年级主任改成了80分,关键是还都update到10001了
班主任:update T t set t.result =‘60’ where t.id='10001'and t.result = '59' ;
年级主任: update T t set t.result =‘80’ where t.id='10001'and t.result = '59' ;
这时候班主任事务先提交,数据库小明成绩改成了60,年级主任事务紧接着提交,小明的成绩又从60改成了80,那么对于班主任来说,他的数据就是更新丢失!!!
所以大家使用起来要注意并发的情况!!
表级锁:一般指的是表结构共享锁。是不可对该表进行DDL操作,但是对于该表数据的DML操作不受影响表级锁讲的比较简单,有兴趣的朋友可以深入探究一下~
原文链接:https://blog.csdn.net/qq_32317661/article/details/80486102