oracle的悲观锁和乐观锁

问select  *from  person  for  update或update person p set p.name ='哈5555'  或 delete from person  加的是行锁还是表锁?
答,表锁。
问select  *from  person where id='1' for  update或者select  *from  person where id in('1','2') for  update 或update person p set p.name ='哈5555' where p.id=5 或  delete from person p where  p.id=6加的是行锁还是表锁?
答,行锁
问,上面的锁是悲观锁还是乐观锁?
答:都是悲观锁,我们知道悲观锁一般就是我们通常说的数据库锁机制,乐观锁一般是指用户自己实现的一种锁机制。


oracle数据库的锁一般可以分为两类,一个是悲观锁,一个是乐观锁,悲观锁一般就是我们通常说的数据库锁机制,乐观锁一般是指用户自己实现的一种锁机制。




乐观锁的应用场景举例:
1. 假设当当网上用户下单买了本书,这时数据库中有条订单号为001的订单,其中有个status字段是’有效’,表示该订单是有效的;
2. 后台管理人员查询到这条001的订单,并且看到状态是有效的;
3. 用户发现下单的时候下错了,于是撤销订单,假设运行这样一条SQL: update order_table set status = ‘取消’ where order_id = 001;
4. 后台管理人员由于在②这步看到状态有效的,这时,虽然用户在③这步已经撤销了订单,可是管理人员并未刷新界面,看到的订单状态还是有效的,于是点击”发货”按钮,将该订单发到物流部门,同时运行类似如下SQL,将订单状态改成已发货:update order_table set status = ‘已发货’ where order_id = 001;


1 悲观锁

所谓的悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁。这样别人拿数据的时候就要等待直到锁的释放。 

数据库行级锁,目的是让数据被查出来的时候就加上锁,然后再执行下面的程序逻辑,这样后面为了操作相同数据而进来的请求,就会在一开始就被拦住(这种效果千万不要以为可以做防重复提交)

在操作DML(insert,update,delete)语句时,oracle会自动加上行级锁,在select * from table for update 【of column】【nowait|wait 3】时,oracle也会自动加锁

1.1 单表 for update

一般在for update 时加nowait,这样就不用等待其他事务执行了,一判断有事务,立马抛出错误。

下面简单说一下 for update的四种情况:

  • select * from table where id = '1001' for update 锁住了这条数据,那么另外一个人对该笔数据进行DML操作或者也执行同样的for update操作时,会检测到这笔数据上有行级锁,那么就会等待着锁释放;这样就会出现一个问题:其他的程序如果需要对这笔数据操作,就需要等,至于等多久要看锁什么时候释放!
  • select * from table where id = '1001' for update nowait,意思就是如果这笔数据上本身加了锁,另外一个人去执行这句SQL的时候,发现加了锁,就会直接抛出异常(ORA-00054:资源正忙),不会等待这笔数据的锁释放
  • select * from table where id = '1001' for update wait 5;意思就是如果这笔数据被锁住,另外一个人如果执行这句SQL后,会等待5秒,如果5秒后这句SQL还没有得到这笔数据的锁,就会抛出异常(ORA-00054:资源正忙)
  • 先执行 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,当有LOCK冲突时会提示错误并结束STATEMENT而不是在那里等待(比如:要查的行已经被其它事务锁了,当前的锁事务与之冲突,加上nowait,当前的事务会结束会提示错误并立即结束 STATEMENT而不再等待).
    WAIT 子句指定等待其他用户释放锁的秒数,防止无限期的等待。
“使用FOR UPDATE WAIT”子句的优点如下:

  • 防止无限期地等待被锁定的行;
  • 允许应用程序中对锁的等待时间进行更多的控制。
  • 对于交互式应用程序非常有用,因为这些用户不能等待不确定
  • 若使用了skip locked,则可以越过锁定的行,不会报告由wait n 引发的‘资源忙’异常报告

1.2 关联表for update

 现在大部分业务都是联表查询,如果用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操作是一样的!

1.3 解除for update 锁的占用

如果在为了方便修改表时,使用了for update 就容易把表锁住,如果这时候及时的commit或者rollback还能释放锁

如果,在提交或者释放之前这个plsql会话连接断了,或者别人占有锁而自己也想操作锁,可以如下操作:

查看锁表进程

select 
t2.username,t2.sid,t2.serial#,t2.logon_time 
from 
v$locked_object t1,v$session t2 
where t1.session_id=t2.sid ; 

 解锁杀死锁表进程

// kill的数字即是,锁表进程中的SID和SERIAL字段的值,把所有的值全部杀掉即可
alter system kill session '1155,39095';

1.4 悲观锁缺点

虽然悲观锁应用起来很简单并且十分安全,与此同时却有两大问题:

  • 锁定:应用的使用者选择一个记录进行更新,然后去吃午饭,但是没有结束或者丢弃该事务。这样其他所有需要更新该记录的用户就必须等待正在进行实务操作的用户回来并且完成该事务或者直到DBA杀掉该不愉快的事务并且释放锁。
  • 死锁:用户A和B同时更新数据库。用户A锁定了一条记录并且试图请求用户B持有的锁,同时用户B也在等待获取用户A持有的锁。两个事务同时进入了无限等待状态即进入死锁状态。

2 乐观锁

所谓的乐观锁:就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。

在乐观锁中,我们有3种常用的做法来实现:

2.1 比对法

第一种就是在数据取得的时候把整个数据都copy到应用中,在进行提交的时候比对当前数据库中的数据和开始的时候更新前取得的数据。当发现两个数据一模一样以后,就表示没有冲突可以提交,否则则是并发冲突,需要去用业务逻辑进行解决。

2.2 版本戳

 第二种乐观锁的做法就是采用版本戳,这个在Hibernate中得到了使用。采用版本戳的话,首先需要在你有乐观锁的数据库table上建立一个新的column,比如为number型,当你数据每更新一次的时候,版本数就会往上增加1。比如同样有2个session同样对某条数据进行操作。两者都取到当前的数据的版本号为1,当第一个session进行数据更新后,在提交的时候查看到当前数据的版本还为1,和自己一开始取到的版本相同。就正式提交,然后把版本号增加1,这个时候当前数据的版本为2。当第二个session也更新了数据提交的时候,发现数据库中版本为2,和一开始这个session取到的版本号不一致,就知道别人更新过此条数据,这个时候再进行业务处理,比如整个Transaction都Rollback等等操作。在用版本戳的时候,可以在应用程序侧使用版本戳的验证,也可以在数据库侧采用Trigger(触发器)来进行验证。不过数据库的Trigger的性能开销还是比较的大,所以能在应用侧进行验证的话还是推荐不用Trigger。

2.3 timestamp型

第三种做法和第二种做法有点类似,就是也新增一个Table的Column,不过这次这个column是采用timestamp型,存储数据最后更新的时间。在Oracle9i以后可以采用新的数据类型,也就是timestamp with time zone类型来做时间戳。这种Timestamp的数据精度在Oracle的时间类型中是最高的,精确到微秒(还没与到纳秒的级别),一般来说,加上数据库处理时间和人的思考动作时间,微秒级别是非常非常够了,其实只要精确到毫秒甚至秒都应该没有什么问题。和刚才的版本戳类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。如果不想把代码写在程序中或者由于别的原因无法把代码写在现有的程序中,也可以把这个时间戳乐观锁逻辑写在Trigger或者存储过程中。

附录:排他锁与共享锁

通过DML语句对一张表的某一行数据进行修改,一个事务开始,背后的步骤是: 
1.对这张表加一个共享锁。这么做是为了防止别的会话通过DDL语句修改这张表的表结构。DDL语句要修改了这张表,就必须给表加上排他锁。但是现在给表加了共享锁了,也就排斥了DDL去加排他锁; 
2.对修改的那一行加一个排他锁,别的会话不能修改这一行。但是我对整张表加的是共享锁而不是排他锁,所以别的会话还是可以修改其他行(也经历1、2两个步骤)。

 

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值