tableA中有多行记录,使用同一个存储过程,当一个session锁定了其中一行,另一个session则选出其他一行,如何实现这个存储过程

解决方案 »

  1.   

    可以
    如果别人把第一条数据  for update 后,你再for update 的时候(加个nowait),就会返回个错误信息(具体什么信息忘了),然后你就可以判断一下,接着处理下一条数据。
      

  2.   

    对,在存储过程里nowait判断是否有错误信息,然后对其进行处理!
      

  3.   

    for update skip locked 并不能真正解决问题数据表中三笔数据:        ID STATUS
    ---------- ----------------
             1 
             2 
             3 A联机:  
    SELECT ID FROM TABLE WHERE STATUS = '' AND ROWNUM < 2 ORDER BY ID ASC FOR UPDATE SKIP LOCKED
    结果:
            ID STATUS
    ---------- ----------------
             1 B联机第1次: SELECT ID FROM TABLE WHERE STATUS = '' AND ROWNUM < 2 ORDER BY ID ASC FOR UPDATE SKIP LOCKED
            ID STATUS
    ---------- ----------------B联机第2次: SELECT ID FROM TABLE WHERE STATUS = '' AND ROWNUM < 3 ORDER BY ID ASC FOR UPDATE SKIP LOCKED
            ID STATUS
    ---------- ----------------
             2主要想达成的是可以每次都只抓出一笔, 但若使用 ROWNUM, 就会依照 ROWNUM 去抓数据, 使用了 SKIP LOCK, 就会取不到.
    因为 ROWNUM 1 的 Record 已经被 A 联机 Lock 住. 这时 B 用 ROWNUM 1 就会没办法抓到. 因为 ROWNUM 1 其实被 A 暂着. 并不会是抓 ROWNUM 2.
    看起来用 ROWNUM 没办法达成取得 SELECT 后 SKIP LOCK 完结果中只取一笔回传. 
    原本是想说可能需要用到 subselect, 但先决条件是用来排序和最后取出来那笔. 一定要是没有被 Lock 住的. 因此若在
    subselct 中就设. 也没办法只 Lock 一笔. 不知道各位专家有没有什么好的解法呢? 谢谢大家...
      

  4.   

    你可以改变思路啊。
    如下:
    把status改成3种: 0 代表空闲,1代表暂存,2代表卖出如果要暂存就把status=0的改成1   update talbeName set status=1 where status=0 and 其它条件。
    如果要出票就把status=0或者1的改成2   update talbeName set status=2 where status in(0,1) and 其它条件。通过上面的方法就可以避免把别人暂存的票重复暂存了。
      

  5.   

    非常感谢楼上,
    但使用这句update talbeName set status=1 where status=0之前肯会有一个select查询,这个select查询同样会引起并发冲突
      

  6.   

    楼主说的也对 
    主要是Oracle的锁机制与MSSQL不一样 
    Oracle是在查询的时候(更新提交前)查出的是历史记录
    而MSSQL是在查询的时候(更新提交前)需要等待提交
      

  7.   

    从来没想过要在这方面控制流程..
    你应该在表内加个Flag来存储状态
    分为几个状态来存储..取的时候取特定
    状态的就行..
      

  8.   

    你没有理解我说的方法。
    每个定票点在暂存票的时候,在更新status的同时,还同时记录了更新为暂存状态的操作员,再接下来的出票或者取消出票的操作,只有对应的操作员自己可以做。所以避免了多人操作同一个暂存状态记录的可能。在将status从0更新到1的过程中,因为增加了status=0的条件,所以避免了两个人同时更新一条记录的可能。因为oracle在更新同一条记录的时候会产生锁等待,只有第一个人更新完成了,第二个人才可以再此更新。在这里,因为第一个人更新status=1后,commit了。第二个人就无法更新同一条记录了,因为status已经由0变为1了。也就是说第二个人更新记录为0,通过判断SQL%ROWCOUNT的值来判断是否暂存成功,通过上边不就解决了并发的问题了吗。
      

  9.   

    问题在于第一个人更新status=1时的某行记录,
    其他的人查询(select id talbeName where status=0 and rownum=1)到还没更新的status=0的这行记录,这样产生了一个竞争,
    那样其他人查询到的这行记录仍旧是status=0
      

  10.   

    大哥,这个可不好用oracle的锁来实现阿。
    业务级别的锁,可以考虑使用一个flag列来实现(0:未/1:锁)。
      

  11.   

    先取一个结果集用于对用户的表示,用户操作定票时:先检查所要更新的记录是否存在、同时加锁:where id=xxx and status=0 for update。如果存在马上进行update,如果出错说明别人他人已加锁,只能由应用来从用户标识用的结果集中取下一条,或者使用新的select取得最新的数据后从中选取一条进行更新,这由业务来决定。
      

  12.   

    SET SERVEROUTPUT ON
    DECLARE
      I      NUMBER       := 0;
      NCOUNT NUMBER       := 0;
      RID    VARCHAR2(50);
      V_IMEI VARCHAR2(25);  
      RES    VARCHAR2(200);  
    BEGIN 
      LOOP
        BEGIN    
          I := I + 1;
          SELECT COUNT(0) INTO NCOUNT FROM LOCK_TEST WHERE USED_FLAG = 0;
          EXIT WHEN NCOUNT < I; 
          SELECT ROWID, IMEI_NO INTO RID, V_IMEI FROM LOCK_TEST WHERE USED_FLAG = 0 AND ROWNUM <= I FOR UPDATE SKIP LOCKED; 
          EXIT WHEN RID IS NOT NULL;
        EXCEPTION     
          WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLCODE) || ' - ' || SQLERRM);
        END;  
      END LOOP;
      IF RID IS NULL THEN
        RES:='NOT GET ROW';
        RETURN;
      END IF;
      UPDATE LOCK_TEST SET USED_FLAG = 1 WHERE ROWID = RID;
      COMMIT;
      INSERT INTO LOCK_TEST_INSERT(IMEI_NO) VALUES(V_IMEI); 
      COMMIT;
      DBMS_OUTPUT.PUT_LINE(RID || ' - ' || V_IMEI || ' - ' || RES);
    EXCEPTION
      WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQLCODE) || ' - ' || SQLERRM); 
    END;
      

  13.   

    楼上们都提供了一些方法, 
    但我要补充一点是,  oracle中,锁是不会阻塞读的.