可以 如果别人把第一条数据 for update 后,你再for update 的时候(加个nowait),就会返回个错误信息(具体什么信息忘了),然后你就可以判断一下,接着处理下一条数据。
对,在存储过程里nowait判断是否有错误信息,然后对其进行处理!
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 一笔. 不知道各位专家有没有什么好的解法呢? 谢谢大家...
你可以改变思路啊。 如下: 把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 其它条件。通过上面的方法就可以避免把别人暂存的票重复暂存了。
非常感谢楼上, 但使用这句update talbeName set status=1 where status=0之前肯会有一个select查询,这个select查询同样会引起并发冲突
先取一个结果集用于对用户的表示,用户操作定票时:先检查所要更新的记录是否存在、同时加锁:where id=xxx and status=0 for update。如果存在马上进行update,如果出错说明别人他人已加锁,只能由应用来从用户标识用的结果集中取下一条,或者使用新的select取得最新的数据后从中选取一条进行更新,这由业务来决定。
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;
如果别人把第一条数据 for update 后,你再for update 的时候(加个nowait),就会返回个错误信息(具体什么信息忘了),然后你就可以判断一下,接着处理下一条数据。
---------- ----------------
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 一笔. 不知道各位专家有没有什么好的解法呢? 谢谢大家...
如下:
把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 其它条件。通过上面的方法就可以避免把别人暂存的票重复暂存了。
但使用这句update talbeName set status=1 where status=0之前肯会有一个select查询,这个select查询同样会引起并发冲突
主要是Oracle的锁机制与MSSQL不一样
Oracle是在查询的时候(更新提交前)查出的是历史记录
而MSSQL是在查询的时候(更新提交前)需要等待提交
你应该在表内加个Flag来存储状态
分为几个状态来存储..取的时候取特定
状态的就行..
每个定票点在暂存票的时候,在更新status的同时,还同时记录了更新为暂存状态的操作员,再接下来的出票或者取消出票的操作,只有对应的操作员自己可以做。所以避免了多人操作同一个暂存状态记录的可能。在将status从0更新到1的过程中,因为增加了status=0的条件,所以避免了两个人同时更新一条记录的可能。因为oracle在更新同一条记录的时候会产生锁等待,只有第一个人更新完成了,第二个人才可以再此更新。在这里,因为第一个人更新status=1后,commit了。第二个人就无法更新同一条记录了,因为status已经由0变为1了。也就是说第二个人更新记录为0,通过判断SQL%ROWCOUNT的值来判断是否暂存成功,通过上边不就解决了并发的问题了吗。
其他的人查询(select id talbeName where status=0 and rownum=1)到还没更新的status=0的这行记录,这样产生了一个竞争,
那样其他人查询到的这行记录仍旧是status=0
业务级别的锁,可以考虑使用一个flag列来实现(0:未/1:锁)。
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;
但我要补充一点是, oracle中,锁是不会阻塞读的.