ITPub博客

首页 > Linux操作系统 > Linux操作系统 > Oracle数据库数据锁机制解析(zt)

Oracle数据库数据锁机制解析(zt)

原创 Linux操作系统 作者:lwitpub 时间:2009-10-12 13:33:44 0 删除 编辑

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

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

先从悲观锁开始说。Oracle用的是行级锁,只是对想锁定的数据才进行锁定,其余的数据不相干,所以在对Oracle表中并发插数据的时候,基本上不会有任何影响。Oracle的悲观锁需要利用一条现有的连接,分成两种方式,从SQL语句的区别来看,就是一种是for update,一种是for update nowait的形式。比如我们看一个例子。首先建立测试用的数据库表。

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

表已创建。


首先我们执行如下的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。也就是没有for update这种锁定数据的语句的话,就不会造成阻塞了。另外一种情况,就是当数据库数据被锁定的时候,也就是执行刚才for update那条sql以后,我们在另外一个session中执行for update nowait后又是什么样呢。比如如下的sql语句。 由于这条语句中是制定采用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中,用这种行级锁带来的性能损失是很小的,只是要注意程序逻辑,不要给你一不小心搞成死锁了就好。而且由于数据的及时锁定,在数据提交时候就不呼出现冲突,可以省去很多恼人的数据冲突处理。缺点就是你必须要始终有一条数据库连接,就是说在整个锁定到最后放开锁的过程中,你的数据库联接要始终保持住。

与悲观锁相对的,我们有了乐观锁。乐观锁一开始也说了,就是一开始假设不会造成数据冲突,在最后提交的时候再进行数据冲突检测。在乐观锁中,最好的方法就是采用Oracle 10g所带有的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语句:SQL> select id,name, location,value,ora_rowscn from test;

        ID NAME           LOCATION           VALUE ORA_ROWSCN
---------- -------------- ------------- ---------- ----------
        10 ACCOUNTING     NEW YORK               1    4926165
        20 RESEARCH       DALLAS                 1    4926165
        30 SALES          CHICAGO                1    4926165
        40 OPERATIONS     BOSTON                 1    4926165

然后执行如下的更新语句:update test set value=2 where id = 20

在数据commit过后,会发现ORA_ROWSCN都发生了变化,如下所示:

SQL> select id,name, location,value,ora_rowscn from test;

 ID    NAME           LOCATION       VALUE ORA_ROWSCN
---------- -------------- ------------- ---------- ----------
        10 ACCOUNTING     NEW YORK               1    4926900
        20 RESEARCH       DALLAS                 2    4926900
        30 SALES          CHICAGO                1    4926900
        40 OPERATIONS     BOSTON                 1    4926900
对于这种情况还是可以有办法解决的,就是利用Oracle 9i提供的ROWDEPENDENCIES建表关键字,这个关键字在Oracle9i中是为了增加行依赖性跟踪特性的,支持推进复制。在 Oracle10g中有可以用来做行级别的ORA_ROWSCN用了。用这个关键字建表以后,在每行会增加一个隐藏的COLUMN,所以每行会增加6个byte的开销。建表语句如下所示:

SQL> CREATE TABLE TEST2(ID,NAME,LOCATION,VALUE,CONSTRAINT test2_pk PRIMARY KEY(ID))ROWDEPENDENCIES AS SELECT deptno, dname, loc, 1 FROM scott.dept;

表已创建。

SQL> select id,name, location,value,ora_rowscn from test2;

        ID NAME           LOCATION           VALUE ORA_ROWSCN
---------- -------------- ------------- ---------- ----------
        10 ACCOUNTING     NEW YORK               1    4943018
        20 RESEARCH       DALLAS                 1    4943018
        30 SALES          CHICAGO                1    4943018
        40 OPERATIONS     BOSTON                 1    4943018

SQL> update test2 set value=2 where id=20;

已更新 1 行。

SQL> select id,name, location,value,ora_rowscn from test2;

        ID NAME           LOCATION           VALUE ORA_ROWSCN
---------- -------------- ------------- ---------- ----------
        10 ACCOUNTING     NEW YORK               1    4943018
        20 RESEARCH       DALLAS                 2
        30 SALES          CHICAGO                1    4943018
        40 OPERATIONS     BOSTON                 1    4943018

可以看到在没提交之前,value为2的ORA_ROWSCN已经为空,

SQL> commit;

提交完成。

SQL> select id,name, location,value,ora_rowscn from test2;

        ID NAME           LOCATION           VALUE ORA_ROWSCN
---------- -------------- ------------- ---------- ----------
        10 ACCOUNTING     NEW YORK               1    4943018
        20 RESEARCH       DALLAS                 2    4943097
        30 SALES          CHICAGO                1    4943018
        40 OPERATIONS     BOSTON                 1    4943018

可以看到这次只有id=20的数据的ORA_ROWSCN发生了更新。所以这个时候我们就可以利用ORA_ROWSCN作为乐观锁,用不着专门再增加一个额外的域了。而且这个域是Oracle负责维护的,开销也不是很大,不过需要Oracle 10g才可以使用。

总之,介绍了Oracle的锁定机制,究竟是悲观锁好还是乐观锁好,其实也是不一定的。Oracle中悲观锁还是很不错的,而且从开始的时候就把数据锁定。免除了后面的很多冲突处理。不过悲观锁需要保持一个Oracle连接,在我们常见的B/S应用中,特别是数据先取得,然后让用户再更新,再返回提交这种流程来说,悲观锁是不大可能的。首先是因为B/S应用中,一般是利用一个连接池,在两次Http Request请求都是不同的数据库Connection。而且也不能锁定一个数据太长时间,否则人人都这么锁定,应用很容易进入死锁状态,这个时候就要采用乐观锁了。而在乐观锁中,ORA_ROWSCN可以说是比较好的选择,不过第一需要重新建表,第二需要在Oracle10g下才有这个特性,所以也不是到处都可以使用,当一个万金油的。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/628922/viewspace-616338/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2009-05-08

  • 博文量
    107
  • 访问量
    418844