子过程过程是否可以锁定并修改其调用过程已锁定的相同行进行更新?

发布于 2024-09-02 05:20:43 字数 639 浏览 3 评论 0原文

以下代码会导致死锁吗?或者它应该可以正常工作吗?我有类似的东西并且它正在工作,但我认为不会。我认为父过程的锁会导致子过程的死锁,但事实似乎并非如此。

如果有效,为什么?我的猜测是,嵌套的 FOR UPDATE 不会陷入死锁,因为它足够聪明,能够意识到它是由具有当前锁的同一过程调用的。

如果 FOO_PROC 不是嵌套过程,这会导致死锁吗?

DECLARE
  FOO_PROC(c_someName VARCHAR2) as
    cursor c1 is select * from awesome_people where person_name = c_someName FOR UPDATE;
  BEGIN
    open c1;
    update awesome_people set person_name = UPPER(person_name);
    close c1;
  END FOO_PROC;

  cursor my_cur is select * from awesome_people where person_name = 'John Doe' FOR UPDATE;
BEGIN
  for onerow in c1 loop
    FOO_PROC(onerow.person_name);
  end loop;
END;

Will the following code lead to a deadlock or should it work without any problem? I've got something similar and it's working but I didn't think it would. I thought the parent procedure's lock would have resulted in a deadlock for the child procedure but it doesn't seem to be.

If it works, why? My guess is that the nested FOR UPDATE is not running into a deadlock because it's smart enough to realize that it is being called by the same procedure that has the current lock.

Would this be a deadlock if FOO_PROC was not a nested procedure?

DECLARE
  FOO_PROC(c_someName VARCHAR2) as
    cursor c1 is select * from awesome_people where person_name = c_someName FOR UPDATE;
  BEGIN
    open c1;
    update awesome_people set person_name = UPPER(person_name);
    close c1;
  END FOO_PROC;

  cursor my_cur is select * from awesome_people where person_name = 'John Doe' FOR UPDATE;
BEGIN
  for onerow in c1 loop
    FOO_PROC(onerow.person_name);
  end loop;
END;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

一绘本一梦想 2024-09-09 05:20:43

不会造成僵局。只有当两个会话更新同一行时才会发生这种情况,因为它们采用乐观锁定策略。以下是发生的情况

一些测试数据:

 SQL> select * from t23
   2  /

 PERSON_NAME
 -----------------------------------------------------------------------------
 Fox in socks
 Mr Knox
 Sam-I-Am
 The Lorax
 John Doe

 SQL>

这是您的匿名(带有更正的 sybtax):。

 SQL> declare
   2      cursor c_jd is
   3          select *
   4          from t23
   5          where person_name = 'John Doe'
   6          for update of person_name;
   7      procedure foo_proc
   8          ( p_name in t23.person_name%type)
   9      is
  10          cursor c_fp is
  11              select *
  12              from t23
  13              where person_name = p_name
  14              for update of person_name;
  15          r_fp c_fp%rowtype;
  16      begin
  17          open c_fp;
  18          fetch c_fp into r_fp;
  19          update t23
  20          set person_name = upper(r_fp.person_name)
  21          where current of c_fp;
  22          close c_fp;
  23      end foo_proc;
  24  begin
  25      for onerow in c_jd loop
  26          foo_proc(onerow.person_name);
  27      end loop;
  28  end;
  29  /

 PL/SQL procedure successfully completed.

 SQL>

这就是结果

SQL>从 t23 选择 *
2 /

 PERSON_NAME
 -----------------------------------------------------------------------------
 Fox in socks
 Mr Knox
 Sam-I-Am
 The Lorax
 JOHN DOE

SQL>

那么它成功了吗?因为FOR UPDATE是会话级别的锁。这两个锁是从同一个会话发出的,因此 Oracle 足够聪明,可以在没有争用的情况下解决它们。然而,如果您要执行类似在 FOO_PROC() 中声明 PRAGMA AUTONOMOUS_TRANSACTION 之类的操作,它会抛出这样的

ORA-00060: deadlock detected while waiting for resource

事实:在同一会话中对 FOR UPDATE 的两次调用不会以这种方式失败,这是架构设计的一个重要部分。如果不查看源代码,就不可能判断过程是否发出锁。因此,当 PROC_A() 调用 PROC_B() 时,它不知道该过程是否发出锁。但 PROC_A() 可以发出自己的锁,并确信此操作不会导致 PROC_B() 失败。这是一件好事,因为它维护了德米特定律并减少了耦合。

当然,您的场景是人为的,并且会在代码审查中被视为不好的做法而被拒绝,但这是一个不同的问题!

编辑

“为了测试这一点,我做了 FOO_PROC
自治并且它没有遇到
僵局;是因为它在
同一个会话?”

您确定吗? AUTONOMOUS_TRANSACTION pragma 恰恰意味着 FOO_PROC() 在其自己的离散会话中运行,因此无法获得锁定:(

SQL> declare
  2      cursor c_jd is
  3          select *
  4          from t23
  5          for update of person_name;
  6      procedure foo_proc
  7          ( p_name in t23.person_name%type)
  8      is
  9          pragma autonomous_transaction;
 10          cursor c_fp is
 11              select *
 12              from t23
 13              where person_name = p_name
 14              for update of person_name;
 15          r_fp c_fp%rowtype;
 16      begin
 17          dbms_output.put_line('Inside FP');
 18          open c_fp;
 19          fetch c_fp into r_fp;
 20          update t23
 21          set person_name = upper(r_fp.person_name)
 22          where current of c_fp;
 23          close c_fp;
 24          commit;
 25      end foo_proc;
 26  begin
 27      for onerow in c_jd loop
 28          dbms_output.put_line('Outer loop START');
 29          foo_proc(onerow.person_name);
 30          dbms_output.put_line('Outer loop END');
 31      end loop;
 32  end;
 33  /
Outer loop START
Inside FP
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 11
ORA-06512: at line 18
ORA-06512: at line 29


SQL>

我添加了一些 DBMS_OUTPUT 语句来显示发生的情况)。

“当你说代码示例时我
如果是不好的做法,你会怎么做
意思是?”

我的意思是有一个循环驱动一个 SELECT 语句,调用另一个从同一个表中进行选择的程序。实际上,它选择了同一行。一般来说,我们应该避免做不必要的工作。您已经有了该行:为什么要阅读又来了?

It won't cause a deadlock. THat can only happen when two sessions update the same row because they are employing an optimistic locking strategy. Here is what happens

Some test data:

 SQL> select * from t23
   2  /

 PERSON_NAME
 -----------------------------------------------------------------------------
 Fox in socks
 Mr Knox
 Sam-I-Am
 The Lorax
 John Doe

 SQL>

This is your anonymous (with corrected sybtax):.

 SQL> declare
   2      cursor c_jd is
   3          select *
   4          from t23
   5          where person_name = 'John Doe'
   6          for update of person_name;
   7      procedure foo_proc
   8          ( p_name in t23.person_name%type)
   9      is
  10          cursor c_fp is
  11              select *
  12              from t23
  13              where person_name = p_name
  14              for update of person_name;
  15          r_fp c_fp%rowtype;
  16      begin
  17          open c_fp;
  18          fetch c_fp into r_fp;
  19          update t23
  20          set person_name = upper(r_fp.person_name)
  21          where current of c_fp;
  22          close c_fp;
  23      end foo_proc;
  24  begin
  25      for onerow in c_jd loop
  26          foo_proc(onerow.person_name);
  27      end loop;
  28  end;
  29  /

 PL/SQL procedure successfully completed.

 SQL>

And this is the outcome

SQL> select * from t23
2 /

 PERSON_NAME
 -----------------------------------------------------------------------------
 Fox in socks
 Mr Knox
 Sam-I-Am
 The Lorax
 JOHN DOE

SQL>

So does it succeed? Because the FOR UPDATE is a session level lock. The two locks are issued from the same session so Oracle is smart enough to resolve them without contention. Howver if you were to do something like declare an PRAGMA AUTONOMOUS_TRANSACTION in FOO_PROC() it would hurl

ORA-00060: deadlock detected while waiting for resource

The fact that two calls to FOR UPDATE in the same session do not not fail in this manner is an important piece of architectural design. It is not possible to tell whether a procedure issues a lock without looking at the source code. So when PROC_A() calls PROC_B() it has no idea whether that procedure issues a lock. But PROC_A() can issues its own lock, confident that this action will not cause PROC_B() to fail. This is a good thing, because it upholds the Law of Demeter and reduces coupling.

Of course, your scenario is artificial, and would be rejected as bad practice in a code review, but that is a different issue!

edit

"To test this I did make FOO_PROC
autonomous and it did not run into a
deadlock; is that because it's in the
same session?"

Are you sure? The AUTONOMOUS_TRANSACTION pragma means precisely that FOO_PROC() runs in its own discrete session, and so fails to get a lock:

SQL> declare
  2      cursor c_jd is
  3          select *
  4          from t23
  5          for update of person_name;
  6      procedure foo_proc
  7          ( p_name in t23.person_name%type)
  8      is
  9          pragma autonomous_transaction;
 10          cursor c_fp is
 11              select *
 12              from t23
 13              where person_name = p_name
 14              for update of person_name;
 15          r_fp c_fp%rowtype;
 16      begin
 17          dbms_output.put_line('Inside FP');
 18          open c_fp;
 19          fetch c_fp into r_fp;
 20          update t23
 21          set person_name = upper(r_fp.person_name)
 22          where current of c_fp;
 23          close c_fp;
 24          commit;
 25      end foo_proc;
 26  begin
 27      for onerow in c_jd loop
 28          dbms_output.put_line('Outer loop START');
 29          foo_proc(onerow.person_name);
 30          dbms_output.put_line('Outer loop END');
 31      end loop;
 32  end;
 33  /
Outer loop START
Inside FP
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 11
ORA-06512: at line 18
ORA-06512: at line 29


SQL>

(I added some DBMS_OUTPUT statements to show what's happening).

"When you said the code example I
provided was bad practice, what do you
mean?"

I meant having a loop driving off a SELECT statement calling another program which selects from the same table. Indeed, which selects the very same row. Generally speaking, we should avoid doing unnecessary work. You already have the row: why read it again?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文