Oracle FOR UPDATE (OF) 游标行为

Oracle FOR UPDATE (OF) Cursor behaviour

我们有一个用于更新特定列的脚本。在此脚本中,我们使用 FOR UPDATE 游标。在脚本的第一个版本中,我们没有使用 FOR UPDATE 子句的 OF 部分。我们发现 here and here 这不应该影响脚本,因为所有连接表的所有行都应该被锁定,因此可以更新。

但是当我们是 运行 脚本时,尽管打印了日志消息,但未对列进行更新 (column_a)。

当我们使用具有 FOR UPDATE OF t1.column_a 的光标更改脚本时,会出现相同的日志消息,但更新是正确的!

谁能解释为什么脚本在没有 OF 子句的情况下无法运行?

Oracle 数据库版本是 'Oracle Database 11g Enterprise Edition Release 11.2.0.3.0' 也用 'Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit' 测试过。

这是已执行脚本的简单版本:

    BEGIN
      -- anonymous procedure
      DECLARE PROCEDURE update_column_a IS
        c_to_find CONSTANT NUMBER := -42;
        c_constant_value CONSTANT VARCHAR2 := 'value';
        CURSOR c_my_cursor IS
          SELECT t1.* 
            FROM table_1 t1, table_2 t2, table_3 t3
           WHERE t1.t2_id = t2.id
             AND t2.t3_id = t3.id
             AND t3.column_b = c_to_find
             -- FOR UPDATE with OF clause works
             -- FOR UPDATE OF t1.column_a;

             -- FOR UPDATE without OF clause does not
             FOR UPDATE;
      BEGIN
        FOR cursor_rec IN c_my_cursor LOOP
          IF cursor_rec.column_a IS NULL OR cursor_rec.column_a = '' THEN
            dbms_output.put_line('Updating column...');
            UPDATE t1 SET column_a = c_constant_value WHERE CURRENT OF   c_my_cursor;
          ELSE
            dbms_output.put_line('Column already set...');
          END IF;
        END LOOP;
      END update_column_a;
      -- anonymous execution
      BEGIN
        update_column_a;
      END;
    END;
    /

根据 Oracle 11G PL/SQL 文档 here:

When SELECT FOR UPDATE queries multiple tables, it locks only rows whose columns appear in the FOR UPDATE clause.

因此,在您的示例中,似乎没有任何行被锁定,current of 可能不起作用。

但是,当我尝试这样做时:

declare
  cursor c is
    select ename, dname
      from emp join dept on dept.deptno = emp.deptno
      for update;
begin
  for r in c loop
     null;
  end loop;
end;

我发现 EMP 和 DEPT 的行被 锁定(从另一个会话更新到其中任何一个都挂起)。

如果我更改代码以尝试更新其中一个表,它对 EMP 工作正常:

declare
  cursor c is
    select ename, dname
      from emp join dept on dept.deptno = emp.deptno
      for update;
begin
  for r in c loop
     update emp
       set ename = upper(ename)
      where current of c;
  end loop;
end;

但是,如果我尝试更新 DEPT,则会出现异常:

ORA-01410: invalid ROWID

这并不让我吃惊,因为我有一个从 EMP 到 DEPT 的外键,并且 EMP 将被游标查询"key-preserved",但 DEPT 不会(即可以出现相同的 DEPT 行)结果中不止一次)。

这向我表明文档是错误的,或者至少是误导性的。但是,我看不出你的代码怎么能不更新行,而不会像我的那样引发错误。

"Can anyone explain why the script does not work without the OFclause?"

你快到了:)

常规的FOR UPDATE是做什么用的? --> 锁定结果集

          SELECT t1.* 
            FROM table_1 t1, table_2 t2, table_3 t3
           WHERE t1.t2_id = t2.id
             AND t2.t3_id = t3.id
             AND t3.column_b = c_to_find
             FOR UPDATE;

因此,这样您就无法更新结果集中的这些表 none。

但是,如果您指定了 FOR UPDATE OF 子句,那么您就是在告诉 ORACLE 您希望在锁中创建一个例外,命名特定的列。

      SELECT t1.* 
        FROM table_1 t1, table_2 t2, table_3 t3
       WHERE t1.t2_id = t2.id
         AND t2.t3_id = t3.id
         AND t3.column_b = c_to_find
         FOR UPDATE OF t1.column_a;

我在其中一本书中看到以下几行。

您可以在 SELECT 中针对多个 table 使用 FOR UPDATE 子句。在这种情况下,仅当 FOR UPDATE 子句引用 table 中的列时,table 中的行才被锁定。在以下示例中,FOR UPDATE 子句不会导致 winterize table:

中的任何锁定行
 CURSOR fall_jobs_cur IS
 SELECT w.task, w.expected_hours,
        w.tools_required,
        w.do_it_yourself_flag
   FROM winterize w, husband_config hc
  WHERE w.year_of_task = TO_CHAR (SYSDATE, 'YYYY')
    AND w.task_id = hc.task_id
    FOR UPDATE OF hc.max_procrastination_allowed;