oracle 在过程中使用FOR UPDATE时,我应该何时提交?

qv7cva1a  于 2023-08-03  发布在  Oracle
关注(0)|答案(3)|浏览(210)

如果在存储过程中使用FOR UPDATE子句,我应该何时“提交”?在关闭打开的光标之后还是在关闭打开的光标之前?下面是我正在使用的程序,我是在正确的方式做吗?

CREATE OR REPLACE PROCEDURE Proc_UpdateCSClientCount(inMerid     IN  VARCHAR2,
                                                     outCliCount  OUT NUMBER,
                                                     outretvalue  OUT NUMBER)
AS
   CURSOR c1 IS
      SELECT CLIENT_COUNT
        FROM OP_TMER_CONF_PARENT
       WHERE MER_ID = inMerid
      FOR UPDATE OF CLIENT_COUNT;
BEGIN
   OPEN c1;
   IF SQL%ROWCOUNT = 1 THEN
      FETCH c1 INTO outCliCount;
      outCliCount := outCliCount + 1;
      UPDATE OP_TMER_CONF_PARENT
         SET CLIENT_COUNT = outCliCount
       WHERE CURRENT OF c1;
   END IF;
   outretvalue := 0;
   CLOSE c1;
   COMMIT;
EXCEPTION
   WHEN no_data_found THEN
      outretvalue := -1;
END;

字符串

pxy2qtax

pxy2qtax1#

您应该在事务结束时提交。我怀疑你能找到一个合理的情况,即事务的结束是在FOR UPDATE循环的中间。
你可能听说过,频繁地承诺是一件好事。这是一个错误的神话,这是totally wrong。在Oracle中,情况正好相反:提交涉及到额外的工作,因此你应该只在所有工作都完成后才提交,而不是在此之前。
此外,从逻辑的Angular 来看,如果您可以从头开始,而不是完成一半的工作,那么从错误中恢复是难以想象的容易。
国际海事组织认为,在程序中作出承诺应极为罕见。调用应用程序应该进行必要的检查,并最终决定是否提交数据。
总之,你不能在FOR UPDATE循环中提交(它会产生一个ORA-01002: fetch out of sequence),这是一件好事。每当你发现自己在一个正常的循环中提交时,你应该问问自己这个提交是否真的有必要--很可能不是。
如果你真的需要提交 * 和 * 只获取一次,那么你是在关闭游标之前还是之后提交都没有关系。

根据代码摘录更新:在你的代码中有很多东西需要纠正(我想它不是直接的生产代码,但仍然是):

  • 永远不会引发异常:只有隐式SELECT INTO可以产生NO_DATA_FOUND
  • 如果前面的语句是SELECT,则SQL%ROWCOUNT为NULL。
  • 您可以使用c1%ROWCOUNT,但这将只返回获取的行数:0在初始open之后。
  • 我主要使用FOR UPDATE NOWAIT,这样两个会话就不会互相阻塞。如果您只使用FOR UPDATE,您还不如使用单个UPDATE,而不是预先使用SELECT
  • 这是一个偏好问题,但返回代码容易出错和exceptions are generally preferred。让错误传播。为什么会有人在一个不存在的id上调用这个函数呢?这可能是调用应用程序/过程中的一个错误,所以你不应该抓住它。

所以你可以像这样重写你的过程:

CREATE OR REPLACE PROCEDURE Proc_UpdateCSClientCount(inMerid     IN  VARCHAR2, 
                                                     outCliCount OUT NUMBER) AS
BEGIN
   -- lock the row, an exception will be raised if this row is locked
   SELECT CLIENT_COUNT + 1
     INTO outCliCount
     FROM OP_TMER_CONF_PARENT
    WHERE MER_ID = inMerid
   FOR UPDATE OF CLIENT_COUNT NOWAIT;
   -- update the row
   UPDATE OP_TMER_CONF_PARENT
      SET CLIENT_COUNT = CLIENT_COUNT + 1
    WHERE MER_ID = inMerid;
END;

字符串

zaqlnxep

zaqlnxep2#

从Oracle文档:
所有行在打开光标时被锁定,而不是在读取它们时被锁定。**提交或回滚事务时,行将被解锁。**由于行不再被锁定,提交后无法从FOR UPDATE游标中获取。
这很重要如果你已经完成了任务(完成了抓取),如果你在关闭游标之前或之后提交,这并不重要。
但是如果需要在读取之间提交,作为一种变通方法,使用带有rowid的update,而不是where current of。doc示例:

DECLARE
   CURSOR c1 IS SELECT last_name, job_id, rowid FROM employees;
   my_lastname   employees.last_name%TYPE;
   my_jobid      employees.job_id%TYPE;
   my_rowid      UROWID;
BEGIN
   OPEN c1;
   LOOP
      FETCH c1 INTO my_lastname, my_jobid, my_rowid;
      EXIT WHEN c1%NOTFOUND;
      UPDATE employees SET salary = salary * 1.02 WHERE rowid = my_rowid;
      -- this mimics WHERE CURRENT OF c1
      COMMIT;
   END LOOP;
   CLOSE c1;
END;
/

字符串

更新(问题编辑后):你可以在一个SQL中做到这一点,而不需要游标。

UPDATE OP_TMER_CONF_PARENT 
set CLIENT_COUNT = CLIENT_COUNT +1 
where MER_ID = inMerid;

更新2。代码应更新如下:

...
open C1;
FETCH C1 into OUTCLICOUNT;
--dbms_output.put_line(' count:'||c1%rowcount);
IF c1%rowcount = 1 THEN
      outCliCount := outCliCount + 1;
...


也就是说:应该在计算受影响的行之前进行提取,受影响的行是c1%rowcount,而不是sql%rowcount。如果你想知道一行是否被更新,你应该给if加上一个else,并给outretvalue参数赋一个特殊的值。

wwwo4jvm

wwwo4jvm3#

如果在关闭游标之前提交,然后再次尝试获取,则会得到INVALID_CURSOR异常。我建议在关闭游标后提交。

相关问题