Оптимизированный способ выбора и обновления больших таблиц

Мне нужно выбрать набор значений, объединив набор больших таблиц, а затем обновить другую большую таблицу из выбранных значений. В настоящее время я следую приведенному ниже подходу. Но я вижу недостаток производительности. Какие существуют альтернативные способы выполнения вышеописанной работы?

Сервер БД: Oracle EE

DECLARE
  CURSOR c1
  IS
    SELECT update_data FOR UPDATE OF abc;
BEGIN
  FOR update_data IN c1
  LOOP
    UPDATE klm
    SET klm.xyz  = update_data.cdf
    WHERE update_data.abc = klm.abc;
  END LOOP;
  COMMIT;
END; 

person era    schedule 05.11.2013    source источник


Ответы (2)


Скорее всего, простое обновление будет работать намного лучше.

Вы можете попробовать с:

update klm t1
set xyz = ( select cdf from update_data t2 where t2.abc = t1.abc ) 
where exists ( select 1 from update_data t2 where t2.abc = t2.abc );

commit;

или, если возможно (имеет PK или уникальный индекс в update_data.abc )

update ( select t1.xyz, t2.cdf from klm t1, update_data t2 where t1.abc = t2.abc ) 
) set xyz = cdf; 

commit;
person igr    schedule 05.11.2013
comment
Спасибо за ответ. Я попробовал второй подход, но получил ошибку Ошибка SQL: ORA-01779: невозможно изменить столбец, который сопоставляется с таблицей без сохранения ключа - person era; 05.11.2013
comment
А также я не могу понять, где существует часть первого решения. Почему вы проверяете t2.abc = t2.abc? Спасибо - person era; 05.11.2013
comment
В отношении первого ответа; update_data снова является сгенерированным. Я не думаю, что это лучший вариант выполнять запрос update_data SELECT дважды (в части SET и в части WHERE EXIST ). Спасибо - person era; 05.11.2013
comment
Когда я попробовал первое решение, я получил ORA-01427: подзапрос с одной строкой возвращает более одной строки. update_data возвращает тысячу строк для обновления. спасибо - person era; 05.11.2013
comment
select в SET должен возвращать только одну строку — вы можете изменить значение ровно только из одной строки. - person igr; 05.11.2013
comment
Вы можете удалить предложение EXISTS в 1-м SQL, только если все строки в первой таблице найдут значение в исходной таблице - в противном случае для него будет установлено значение NULL, если в исходной таблице не найдено ни одной строки. - person igr; 05.11.2013
comment
Спасибо игр. Мой SELECT возвращает все строки, которые необходимо обновить. Обновление построчно не будет работать, так как мой SELECT занимает разумное время. - person era; 05.11.2013

Если у вас есть проблемы с производительностью при циклическом просмотре каждой записи, но таблица слишком велика для одного обновления, вы можете рассмотреть возможность пакетного обновления с помощью BULK INTO ... LIMIT и FORALL.

CREATE TABLE klm (abc INTEGER, xyz INTEGER);
CREATE TABLE update_data (abc INTEGER, cdf INTEGER);

-- Have pairs of numbers (1000 rows)
INSERT INTO klm SELECT rownum, rownum FROM dual CONNECT BY level <= 1000;
-- Update every second row with 9999
INSERT INTO update_data SELECT rownum * 2, 9999 FROM dual CONNECT BY level <= 500;

DECLARE
  CURSOR c1
  IS
    -- Select the key to be updated and the new value
    SELECT abc, cdf FROM update_data;
  -- Table type and table variable to store rows fetched from the cursor
  TYPE t_update IS TABLE OF c1%rowtype;
  update_tab t_update;
BEGIN
  OPEN c1;
  LOOP
    -- Fetch next 30 rows into update table
    FETCH c1 BULK COLLECT INTO update_tab LIMIT 30;
    -- Exit when there were no more rows fetched
    EXIT WHEN update_tab.count = 0;
    -- This is the key point; uses update_tab to bulk-bind UPDATE statement
    -- and run it for 30 rows in a single context switch
    FORALL i IN 1..update_tab.count
      UPDATE klm
      SET klm.xyz  = update_tab(i).cdf
      WHERE update_tab(i).abc = klm.abc;
    COMMIT;
  END LOOP;
  CLOSE c1;
END;
/

Объяснение этому заключается в том, что Oracle фактически имеет отдельные механизмы, выполняющие операторы SQL и программы PL/SQL. Всякий раз, когда процедура сталкивается с оператором SQL, она передает его механизму SQL для выполнения. Это называется «переключением контекста» и занимает много времени, особенно если выполняется в цикле.

Массовое связывание направлено на снижение этих накладных расходов за счет переключения контекста только один раз для каждой записи [массового размера]. Опять же, это, конечно, не так эффективно, как отдельная операция DML, но для больших таблиц или сложных запросов это может быть наилучшим возможным решением.

Я использовал описанный выше метод для обновления таблиц с записями 100–500 М с размером пакета 10–100 КБ, и он работал нормально. Но вам нужно поэкспериментировать с размером пакета в вашей среде для достижения наилучшей производительности.

person Kombajn zbożowy    schedule 05.11.2013
comment
Спасибо, Комбайн. Я могу понять LOOP. Но не очистить выбранную часть. Можете немного объяснить логику выбора? - person era; 07.11.2013
comment
Оператор SELECT просто выбирает ключ (abc) и новое значение (cdf) из таблицы update_data, если это то, что вы имеете в виду. В любом случае, я добавил некоторые комментарии, надеюсь, теперь это будет более понятно. Также ознакомьтесь со статьей Oracle Base. при массовом связывании - person Kombajn zbożowy; 07.11.2013