Может ли процедура подпрограммы заблокировать и изменить те же строки для ОБНОВЛЕНИЯ, что ее вызывающая процедура уже заблокирована? - PullRequest
2 голосов
/ 23 мая 2010

Приведет ли следующий код к тупику или он должен работать без проблем? У меня есть что-то похожее, и оно работает, но я не думаю, что это будет. Я думал, что блокировка родительской процедуры привела бы к тупику для дочерней процедуры, но, похоже, это не так.

Если это работает, почему? Я предполагаю, что вложенный 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;

1 Ответ

3 голосов
/ 23 мая 2010

Это не приведет к тупику. Это может произойти только тогда, когда два сеанса обновляют одну и ту же строку, поскольку они используют оптимистическую стратегию блокировки. Вот что происходит

Некоторые данные испытаний:

 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> select * from t23 2 /

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

SQL>

Так ли это успешно? Потому что FOR UPDATE - это блокировка уровня сеанса. Эти две блокировки создаются в одном сеансе, поэтому Oracle достаточно умен, чтобы разрешить их без конфликтов. Как бы то ни было, если бы вы сделали что-то вроде объявления PRAGMA AUTONOMOUS_TRANSACTION в FOO_PROC (), это привело бы к

ORA-00060: deadlock detected while waiting for resource

Тот факт, что два вызова FOR UPDATE в одном сеансе не перестают работать таким образом, является важной частью архитектурного проектирования. Невозможно определить, выдает ли процедура блокировку, не глядя на исходный код. Поэтому, когда PROC_A () вызывает PROC_B (), он не имеет представления, вызывает ли эта процедура блокировку. Но PROC_A () может выдавать свою собственную блокировку, будучи уверенным, что это действие не приведет к сбою PROC_B (). Это хорошая вещь, потому что она поддерживает Закон Деметры и уменьшает связь.

Конечно, ваш сценарий является искусственным и будет отвергнут как плохая практика в обзоре кода, но это другая проблема!

редактировать

"Чтобы проверить это, я сделал FOO_PROC автономный и не столкнулся с тупиковой; это потому что это в тот же сеанс? "

Вы уверены? Прагма AUTONOMOUS_TRANSACTION означает, что 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, вызывающий другую программу, которая выбирает из той же таблицы. Действительно, который выбирает тот же ряд. Вообще говоря, мы должны избегать ненужной работы. У вас уже есть строка: зачем ее читать?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...