from http://database.youkuaiyun.com/page/7ed4f925-653b-4f09-97a3-1e7e5a4d97aa 来自:java060515
<script src="http://z.youkuaiyun.com/adjs.php?n=132834432&what=zone:112&charset=utf-8&exclude=,&referer=http%3a//www.google.com.sg/search%3fq%3dfor+update+%25e9%2594%2581%25e5%25ae%259a%25e6%2595%25b0%25e6%258d%25ae%25e5%25ba%2593++csdn%26ie%3dutf-8%26oe%3dutf-8%26aq%3dt%26rls%3dorg.mozilla%3aen-us%3aofficial%26client%3dfirefox-a" type="text/javascript"><!-- mce:1 // --></script>
先从悲观锁开始说。在SQLServer等其余很多数据库中,数据的锁定通常采用页级锁的方式,也就是说对一张表内的数据是一种串行化的更新插入机制,在任何时间同一张表只会插1条数据,别的想插入的数据要等到这一条数据插完以后才能依次插入。带来的后果就是性能的降低,在多用户并发访问的时候,当对一张表进行频繁操作时,会发现响应效率很低,数据库经常处于一种假死状态。而Oracle用的是行级锁,只是对想锁定的数据才进行锁定,其余的数据不相干,所以在对Oracle表中并发插数据的时候,基本上不会有任何影响。
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中你的检索语句如下所示。select * from test where id = 10。
由于这条语句中是制定采用nowait方式来进行检索,所以当发现数据被别的session锁定中的时候,就会迅速返回ORA-00054错误,内容是资源正忙, 但指定以 NOWAIT方式获取资源。所以在程序中我们可以采用nowait方式迅速判断当前数据是否被锁定中,如果锁定中的话,就要采取相应的业务措施进行处理。select * from test where id = 10 for update nowait。
那这里另外一个问题,就是当我们锁定住数据的时候,我们对数据进行更新和删除的话会是什么样呢。比如同样,我们让第一个Session锁定住id=10的那条数据,我们在第二个session中执行如下语句。update test set value=2 where id = 10。
这个时候我们发现update语句就好像select for update语句一样也停住卡在这里,当你第一个session放开锁定以后update才能正常运行。当你update运行后,数据又被你update语句锁定住了,这个时候只要你update后还没有commit,别的session照样不能对数据进行锁定更新等等。
总之,Oracle中的悲观锁就是利用Oracle的Connection对数据进行锁定。在Oracle中,用这种行级锁带来的性能损失是很小的,只是要注意程序逻辑,不要给你一不小心搞成死锁了就好。而且由于数据的及时锁定,在数据提交时候就不呼出现冲突,可以省去很多恼人的数据冲突处理。缺点就是你必须要始终有一条数据库连接,就是说在整个锁定到最后放开锁的过程中,你的数据库联接要始终保持住。
第一种就是在数据取得的时候把整个数据都copy到应用中, 在进行提交的时候比对当前数据库中的数据和开始的时候更新前取得的数据。当发现两个数据一模一样以后,就表示没有冲突可以提交,否则则是并发冲突,需要去用业务逻辑进行解决。
第二种乐观锁的做法就是采用版本戳,这个在Hibernate中得到了使用。 采用版本戳的话,首先需要在你有乐观锁的数据库table上建立一个新的column,比如为number型,当你数据每更新一次的时候,版本数就会往上增加1。比如同样有2个session同样对某条数据进行操作。两者都取到当前的数据的版本号为1,当第一个session进行数据更新后,在提交的时候查看到当前数据的版本还为1,和自己一开始取到的版本相同。就正式提交,然后把版本号增加1,这个时候当前数据的版本为2。当第二个session也更新了数据提交的时候,发现数据库中版本为2,和一开始这个session取到的版本号不一致,就知道别人更新过此条数据,这个时候再进行业务处理,比如整个Transaction都Rollback等等操作。在用版本戳的时候,可以在应用程序侧使用版本戳的验证,也可以在数据库侧采用Trigger(触发器)来进行验证。不过数据库的Trigger的性能开销还是比较的大,所以能在应用侧进行验证的话还是推荐不用Trigger。
select owa_opt_lock.checksum (to_char(sysdate,'YYYYMMDDHHMISS') ) from dual
在这里我用这个OWA_OPT_LOCK.CHECKSUM函数对当前的系统时间进行了散列,用这个方法生成的出土的可能性是65536,因为生成的散列算法是一个16位置的值,所以值范围也就是65536个值而已。
第二种散列算法采用Oracle8i 8.1.7以后提供的DBMS_OBFUSCATION_TOOLKIT.MD5方法,不过这个方法不能直接在SQL语句里面使用,需要在存储过程中调用。所以可以先写一个共通的存储过程的Function,然后利用这个function去进行加密。
第三种散列方法就是用Oracle 10g Release1种提供的DBMS_CRYPTO.HASH方法。这个方法可以计算一个SHA-1或者是MD5摘要,所以如果实际情况下使用Oracle10g的数据库,建议采用这个方法。不过和第二种类似,这个方法也是需要采用存储过程才能够编写,不能直接应用在SQL语句中的。
最后一种做法就是采用Oracle10g所带有的ORA_ROWSCN函数来进行乐观锁。ORA_ROWSCN是根据系统最后更新时间来进行计算。这个ORA_ROWSCN在默认情况下是采用数据块为单位的,也就是一个数据库块(block)上共享一个ORA_ROWSCN,当数据更新的时候,这个block快的ORA_ROWSCN就会自动更新。所以在默认情况下的话,有可能出现假冲突的情况。比如A,B,C,D四条数据都在一个block上,这个时候A数据更新了,ORA_ROWSCN也会更新,这个时候因为ABCD四条数据存储在一个block上,所以BCD的ORA_ROWSCN也更新过了,其实BCD三条数据并没有更新过,这个就造成了假更新的情况出现。见下面的SQL语句:select id, name, location, value,ora_rowscn from test。