Oracle的悲观锁和乐观锁

  为了获得最大的性能,通常数据库都有并发机制,不过带来的问题就是数据访问的冲突。为了解决这个问题,大多数数据库用的方法就是数据的锁定。sql

  数据的锁定分为两种方法,第一种叫作悲观锁,第二种叫作乐观锁。什么叫悲观锁呢,悲观锁顾名思义,就是对数据的冲突采起一种悲观的态度,也就是说假设数据确定会冲突,因此在数据开始读取的时候就把数据锁定住。而乐观锁就是认为数据通常状况下不会形成冲突,因此在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,若是发现冲突了,则让用户返回错误的信息,让用户决定如何去作。数据库

  先从悲观锁开始说。在SqlServer等其他不少数据库中,数据的锁定一般采用页级锁的方式,也就是说对一张表内的数据是一种串行化的更新插入机制,在任什么时候间同一张表只会插1条数据,别的想插入的数据要等到这一条数据插完之后才能依次插入。带来的后果就是性能的下降,在多用户并发访问的时候,当对一张表进行频繁操做时,会发现响应效率很低,数据库常常处于一种假死状态。而Oracle用的是行级锁,只是对想锁定的数据才进行锁定,其他的数据不相干,因此在对Oracle表中并发插数据的时候,基本上不会有任何影响。session

注:对于悲观锁是针对并发的可能性比较大,而通常在咱们的应用中用乐观锁足以。并发

  Oracle的悲观锁须要利用一条现有的链接,分红两种方式,从SQL语句的区别来看,就是一种是 for update,一种是 for update nowait 的形式。好比咱们看一个例子。首先创建测试用的数据库表。性能

CREATE TABLE TEST(ID,NAME,LOCATION,VALUE,CONSTRAINT test_pk PRIMARY KEY(ID))AS SELECT deptno, dname, loc, 1 FROM scott.dept;

  这里咱们利用了Oracle的Sample的scott用户的表,把数据copy到咱们的test表中。首先咱们看一下for update锁定方式。首先咱们执行以下的select for update语句。测试

select * from test where id = 10 for update

  经过这条检索语句锁定之后,再开另一个sql*plus窗口进行操做,再把上面这条sql语句执行一便,你会发现sqlplus好像死在那里了,好像检索不到数据的样子,可是也不返回任何结果,就属于卡在那里的感受。这个时候是什么缘由呢,就是一开始的第一个Session中的select for update语句把数据锁定住了。因为这里锁定的机制是wait的状态(只要不表示nowait那就是wait),因此第二个Session(也就是卡住的那个sql*plus)中当前这个检索就处于等待状态。当第一个session最后commit或者rollback以后,第二个session中的检索结果就是自动跳出来,而且也把数据锁定住。不过若是你第二个session中你的检索语句以下所示。spa

select * from test where id = 10

  也就是没有for update这种锁定数据的语句的话,就不会形成阻塞了。另一种状况,就是当数据库数据被锁定的时候,也就是执行刚才for update那条sql之后,咱们在另一个session中执行for update nowait后又是什么样呢。好比以下的sql语句。 因为这条语句中是制定采用nowait方式来进行检索,因此当发现数据被别的session锁定中的时候,就会迅速返回ORA-00054错误,内容是资源正忙, 但指定以 NOWAIT 方式获取资源。因此在程序中咱们能够采用nowait方式迅速判断当前数据是否被锁定中,若是锁定中的话,就要采起相应的业务措施进行处理。.net

select * from test where id = 10 for update nowait;

  那这里另一个问题,就是当咱们锁定住数据的时候,咱们对数据进行更新和删除的话会是什么样呢。好比一样,咱们让第一个Session锁定住id=10的那条数据,咱们在第二个session中执行以下语句。code

update test set value=2 where id = 10

  这个时候咱们发现update语句就好像select for update语句同样也停住卡在这里,当你第一个session放开锁定之后update才能正常运行。当你update运行后,数据又被你update语句锁定住了,这个时候只要你update后尚未commit,别的session照样不能对数据进行锁定更新等等。blog

  总之,Oracle中的悲观锁就是利用Oracle的Connection对数据进行锁定。在Oracle中,用这种行级锁带来的性能损失是很小的,只是要注意程序逻辑,不要给你一不当心搞成死锁了就好。并且因为数据的及时锁定,在数据提交时候就不呼出现冲突,能够省去不少恼人的数据冲突处理。缺点就是你必需要始终有一条数据库链接,就是说在整个锁定到最后放开锁的过程当中,你的数据库联接要始终保持住。与悲观锁相对的,咱们有了乐观锁。乐观锁一开始也说了,就是一开始假设不会形成数据冲突,在最后提交的时候再进行数据冲突检测。在乐观锁中,咱们有3种经常使用的作法来实现。

  1. 第一种就是在数据取得的时候把整个数据都copy到应用中,在进行提交的时候比对当前数据库中的数据和开始的时候更新前取得的数据。当发现两个数据如出一辙之后,就表示没有冲突能够提交,不然则是并发冲突,须要去用业务逻辑进行解决。
  2. 第二种乐观锁的作法就是采用版本戳,这个在Hibernate中获得了使用。采用版本戳的话,首先须要在你有乐观锁的数据库table上创建一个新的column,好比为number型,当你数据每更新一次的时候,版本数就会往上增长1。好比一样有2个session一样对某条数据进行操做。二者都取到当前的数据的版本号为1,当第一个session进行数据更新后,在提交的时候查看到当前数据的版本还为1,和本身一开始取到的版本相同。就正式提交,而后把版本号增长1,这个时候当前数据的版本为2。当第二个session也更新了数据提交的时候,发现数据库中版本为2,和一开始这个session取到的版本号不一致,就知作别人更新过此条数据,这个时候再进行业务处理,好比整个Transaction都Rollback等等操做。在用版本戳的时候,能够在应用程序侧使用版本戳的验证,也能够在数据库侧采用Trigger(触发器)来进行验证。不过数据库的Trigger的性能开销仍是比较的大,因此能在应用侧进行验证的话仍是推荐不用Trigger。
  3. 第三种作法和第二种作法有点相似,就是也新增一个Table的Column,不过此次这个column是采用timestamp型,存储数据最后更新的时间。在Oracle9i之后能够采用新的数据类型,也就是timestamp with time zone类型来作时间戳。这种Timestamp的数据精度在Oracle的时间类型中是最高的,精确到微秒(还没与到纳秒的级别),通常来讲,加上数据库处理时间和人的思考动做时间,微秒级别是很是很是够了,其实只要精确到毫秒甚至秒都应该没有什么问题。和刚才的版本戳相似,也是在更新提交的时候检查当前数据库中数据的时间戳和本身更新前取到的时间戳进行对比,若是一致则OK,不然就是版本冲突。若是不想把代码写在程序中或者因为别的缘由没法把代码写在现有的程序中,也能够把这个时间戳乐观锁逻辑写在Trigger或者存储过程当中。

 

转自:http://blog.itpub.net/12158104/viewspace-374745

相关文章
相关标签/搜索