массовое использование для обновления

Я столкнулся с интересной и неожиданной проблемой при обработке записей в Oracle (11g) с помощью BULK COLLECT.

Следующий код работал отлично, обрабатывая более миллиона записей без проблем:

-- Define cursor
cursor My_Data_Cur Is
Select col1
      ,col2
from My_Table_1;
…

-- Open the cursor
open My_Data_Cur;

-- Loop through all the records in the cursor
loop

  -- Read the first group of records
  fetch My_Data_Cur
  bulk collect into My_Data_Rec
  limit 100;

  -- Exit when there are no more records to process
  Exit when My_Data_Rec.count = 0;

  -- Loop through the records in the group
  for idx in 1 .. My_Data_Rec.count
  loop
    … do work here to populate a records to be inserted into My_Table_2 …
  end loop;

  -- Insert the records into the second table
  forall idx in 1 .. My_Data_Rec.count
  insert into My_Table_2…;

  -- Delete the records just processed from the source table
  forall idx in 1 .. My_Data_Rec.count
  delete from My_Table_1 …;

  commit;
end loop;

Поскольку в конце обработки каждой группы из 100 записей (ограничение 100) мы удаляем только что прочитанные и обработанные записи, я подумал, что было бы неплохо добавить синтаксис «для обновления» в определение курсора, чтобы другой процесс мог не обновлять никакие записи между временем чтения данных и временем удаления записи.

Итак, единственное, что я изменил в коде, это…

cursor My_Data_Cur
is
  select col1
        ,col2
from My_Table_1
for update;

Когда я запускал пакет PL/SQL после этого изменения, задание обрабатывает только 100 записей, а затем завершается. Я подтвердил, что это изменение вызывает проблему, удалив «для обновления» из курсора, и пакет снова обработал все записи из исходной таблицы.

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

Заранее спасибо за вашу помощь,

Грант


person Grant    schedule 07.02.2014    source источник


Ответы (2)


Проблема в том, что вы пытаетесь выполнить выборку через фиксацию.

Когда вы открываете My_Data_Cur с предложением for update, Oracle должен заблокировать каждую строку в таблице My_Data_1, прежде чем он сможет вернуть какие-либо строки. Когда вы commit, Oracle должен снять все эти блокировки (блокировки, которые создает Oracle, не охватывают транзакции). Поскольку у курсора больше нет запрошенных вами блокировок, Oracle должен закрыть курсор, так как он больше не может удовлетворять предложению for update. Таким образом, вторая выборка должна возвращать 0 строк.

Наиболее логичным подходом почти всегда было бы удалить commit и сделать все это за одну транзакцию. Если вам действительно нужны отдельные транзакции, вам нужно будет открывать и закрывать курсор для каждой итерации цикла. Скорее всего, вы захотите сделать что-то, чтобы ограничить курсор, чтобы он возвращал только 100 строк каждый раз, когда он открывается (т. строку, отличную от 100, которую вы обработали и удалили, чтобы снять блокировку каждый раз в цикле.

person Justin Cave    schedule 07.02.2014
comment
Джастин - Спасибо за быстрый ответ, и я ценю тот факт, что вы указали несколько вариантов! - person Grant; 08.02.2014

Добавление к объяснению Джастина.

Вы должны были увидеть приведенное ниже сообщение об ошибке. Не уверен, что ваш обработчик Exception подавил это.

И само сообщение многое объясняет!

Для такого рода обновлений лучше создать теневую копию основной таблицы, и пусть публичный синоним указывает на нее. В то время как некоторый идентификатор пакета создает частный синоним для нашей основной таблицы и выполняет пакетные операции, чтобы упростить его обслуживание.

Error report -
ORA-01002: fetch out of sequence
ORA-06512: at line 7
01002. 00000 -  "fetch out of sequence"
*Cause:    This error means that a fetch has been attempted from a cursor
           which is no longer valid.  Note that a PL/SQL cursor loop
           implicitly does fetches, and thus may also cause this error.
           There are a number of possible causes for this error, including:
           1) Fetching from a cursor after the last row has been retrieved
           and the ORA-1403 error returned.
           2) If the cursor has been opened with the FOR UPDATE clause,
           fetching after a COMMIT has been issued will return the error.
           3) Rebinding any placeholders in the SQL statement, then issuing
           a fetch before reexecuting the statement.
*Action:   1) Do not issue a fetch statement after the last row has been
           retrieved - there are no more rows to fetch.
           2) Do not issue a COMMIT inside a fetch loop for a cursor
           that has been opened FOR UPDATE.
           3) Reexecute the statement after rebinding, then attempt to
           fetch again.

Кроме того, вы можете изменить свою логику, используя rowid

Пример для документов:

DECLARE
-- if "FOR UPDATE OF salary" is included on following line, an error is raised
   CURSOR c1 IS SELECT e.*,rowid FROM employees e;
   emp_rec  employees%ROWTYPE;
BEGIN
   OPEN c1;
   LOOP
     FETCH c1 INTO emp_rec; -- FETCH fails on the second iteration with FOR UPDATE
     EXIT WHEN c1%NOTFOUND;
     IF emp_rec.employee_id = 105 THEN
       UPDATE employees SET salary = salary * 1.05 WHERE rowid = emp_rec.rowid;
         -- this mimics WHERE CURRENT OF c1
     END IF;
     COMMIT;  -- releases locks
   END LOOP;
END;
/

Вы должны получить запись построчно!! немедленно обновите его, используя ROWID AND COMMIT. А затем приступайте к следующему ряду!

Но для этого вам придется отказаться от варианта Bulk Binding.

person Maheswaran Ravisankar    schedule 07.02.2014
comment
Этот код является динамическим SQL, и я неправильно перехватывал исключение, поэтому я не получил сообщение об ошибке. Старый код не выполнял массовый сбор и работал около 3 дней. Я переписал с помощью массового сбора, и теперь он работает примерно за 6 часов. Я пропустил параметр FOR UPDATE в своем переписывании, и он был обнаружен во время проверки кода. Я удалил COMMIT, что просто означает, что процесс будет работать по принципу «все или ничего», я добавил COMMIT в код только для того, чтобы мы могли при необходимости остановить задание и не потерять уже проделанную работу. Спасибо за ваш вклад. - person Grant; 09.02.2014
comment
@Grant Спасибо, это просто признание моих проблем с работой!! - person Maheswaran Ravisankar; 09.02.2014