оптимизация дублирующего оператора удаления Oracle

У меня есть 2 оператора удаления, выполнение которых занимает много времени. В предложении where есть несколько индексов для столбцов.

Что такое дубликат? Если 2 или более записей имеют одинаковые значения в столбцах id,cid,type,trefid,ordrefid,amount и paydt, то это дубликаты.

DELETE удаляет около 1 миллиона записей.

Можно ли их как-то переписать, чтобы сделать это быстрее.

DELETE FROM TABLE1 A WHERE loaddt < (
    SELECT max(loaddt) FROM TABLE1 B
    WHERE 
    a.id=b.id and
    a.cid=b.cid and
    NVL(a.type,'-99999') = NVL(b.type,'-99999') and
    NVL(a.trefid,'-99999')=NVL(b.trefid,'-99999') and
    NVL(a.ordrefid,'-99999')= NVL(b.ordrefid,'-99999') and
    NVL(a.amount,'-99999')=NVL(b.amount,'-99999') and
    NVL(a.paydt,TO_DATE('9999-12-31','YYYY-MM-DD'))=NVL(b.paydt,TO_DATE('9999-12-31','YYYY-MM-DD'))
);

    COMMIT;

DELETE FROM TABLE1 a where rowid > (
    Select min(rowid) from TABLE1 b
    WHERE 
    a.id=b.id and
    a.cid=b.cid and
    NVL(a.type,'-99999') = NVL(b.type,'-99999') and
    NVL(a.trefid,'-99999')=NVL(b.trefid,'-99999') and
    NVL(a.ordrefid,'-99999')= NVL(b.ordrefid,'-99999') and
    NVL(a.amount,'-99999')=NVL(b.amount,'-99999') and
    NVL(a.paydt,TO_DATE('9999-12-31','YYYY-MM-DD'))=NVL(b.paydt,TO_DATE('9999-12-31','YYYY-MM-DD'))
);

commit;

Объясните план:

DELETE  TABLE1         

    HASH JOIN 1296491 
    Access Predicates 

        AND 
        A.ID=ITEM_1 
        A.CID=ITEM_2 
        ITEM_3=NVL(TYPE,'-99999') 
        ITEM_4=NVL(TREFID,'-99999') 
        ITEM_5=NVL(ORDREFID,'-99999') 
        ITEM_6=NVL(AMOUNT,(-99999)) 
        ITEM_7=NVL(PAYDT,TO_DATE(' 9999-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss')) 

    Filter Predicates 
        LOADDT<MAX(LOADDT)

    TABLE ACCESS  TABLE1     FULL    267904 
    VIEW VW_SQ_1         690385 
    SORT GROUP BY    690385 
        TABLE ACCESS TABLE1      FULL    267904 

person Ram    schedule 01.10.2012    source источник
comment
что именно долго? Вы действительно проблема, что вы не знаете, где вы находитесь в процессе удаления (% завершения)? Кроме того, какова ваша предполагаемая логика с rowid в № 2?   -  person tbone    schedule 01.10.2012
comment
Длительное время = 15 часов. Я действительно хочу сократить время, необходимое для удаления дубликатов. Мы загружаем новые данные каждую неделю и удаляем повторяющиеся записи за предыдущие недели, поэтому в № 2 мы пытаемся удалить записи, если условия (предложение where) совпадают, но с более низким идентификатором строки. Нижний идентификатор строки связан с записями, вставленными на предыдущей неделе. Надеюсь, я имею смысл здесь.   -  person Ram    schedule 01.10.2012
comment
Что такое дубликат? Если 2 или более записей имеют одинаковые значения в столбцах id,cid,type,trefid,ordrefid,amount и paydt, то есть дубликаты.   -  person Ram    schedule 01.10.2012
comment
вы понимаете, что rowids нельзя (надежно) использовать для сортировки по времени. Они говорят, где существует ряд, а не когда.   -  person tbone    schedule 01.10.2012
comment
tbone, это я понимаю. По этой причине у нас есть удаление дубликатов на основе loaddt до этого.   -  person Ram    schedule 01.10.2012
comment
Я также думаю, что ROWID - опасная вещь. Также я думаю, вам нужно выяснить, что медленно. Это delete или select? Можете ли вы вставить EXPLAIN PLAN или хотя бы какие знаки у вас есть? В большинстве случаев медленное является медленным из-за неправильного индекса. Другая идея заключается в том, что вы перезаписываете, а не удаляете после загрузки. Также, возможно, вы даете для удаления список, идентифицированный первичным уникальным индексом, например WHERE ID IN (..JOIN SELECT...). Как говорится, это может быть разное.   -  person hol    schedule 01.10.2012


Ответы (3)


Насколько большой стол? Если количество удаленных строк составляет до 12%, вы можете подумать об index. Не могли бы вы как-то разделить свою таблицу - например, по неделям, а затем сканировать только текущую неделю?

Может быть, это могло бы быть более эффективным. Когда вы используете агрегатную функцию, оракул должен пройти через все соответствующие строки (в вашем случае полное сканирование), но когда вы используете существования, он останавливается, когда найдено первое вхождение. (и, конечно, запрос был бы намного быстрее, если бы был один индекс на основе функции (из-за NVL) для всех столбцов в предложении where)

DELETE FROM TABLE1 A 
WHERE exists (
SELECT 1 
FROM TABLE1 B
WHERE 
A.loaddt != b.loaddt
a.id=b.id and
a.cid=b.cid and
NVL(a.type,'-99999') = NVL(b.type,'-99999') and
NVL(a.trefid,'-99999')=NVL(b.trefid,'-99999') and
NVL(a.ordrefid,'-99999')= NVL(b.ordrefid,'-99999') and
NVL(a.amount,'-99999')=NVL(b.amount,'-99999') and
NVL(a.paydt,TO_DATE('9999-12-31','YYYY-MM-DD'))=NVL(b.paydt,TO_DATE('9999-12-31','YYYY-MM-DD'))
);
person jakub.petr    schedule 01.10.2012
comment
+1 за упоминание о разбиении. (Хотя я не уверен, откуда вы взяли 12%?) - person Jon Heller; 02.10.2012
comment
12% основано на моем опыте (из предполагаемого размера строки и размера блока данных в примере — у нас есть похожие таблицы), но это может быть что угодно от 1% до 100%. Дополнительная информация здесь я отмечаю, что слепое добавление индекса может не помочь. (и это замедляет операцию удаления, поэтому может даже ухудшить производительность) - person jakub.petr; 02.10.2012
comment
jakub DELETE удаляет гораздо больше 12% записей. Также в запросе у вас есть A.loaddt != b.loaddt, должно ли это быть A.loaddt = b.loaddt? Извините, не могли бы вы объяснить, что вы делаете в этом запросе? - person Ram; 04.10.2012
comment
Спасибо, Якуб, это DELETE было очень быстрым! Я просто изменил A.loaddt ‹ b.loaddt - person Ram; 04.10.2012

Хотя некоторые могут не согласиться, я сторонник процедурного запуска больших и длительных удалений. На мой взгляд, гораздо проще контролировать и отслеживать прогресс (и ваш администратор баз данных будет любить вас больше ;-) Кроме того, не уверен, почему вам нужно присоединять таблицу1 к себе, чтобы идентифицировать дубликаты (и мне было бы любопытно, если вы когда-нибудь столкнетесь с снимок слишком старых проблем с вашим текущим подходом). Вам также не нужно несколько операторов удаления, все дубликаты должны обрабатываться в одном процессе. Наконец, вы должны проверить, ПОЧЕМУ вы постоянно повторно вводите дубликаты каждую неделю, и, возможно, изменить процесс загрузки (возможно, выполняя слияние/upsert, а не все вставки).

Тем не менее, вы можете попробовать что-то вроде:

-- first create mat view to find all duplicates
create materialized view my_dups_mv
tablespace my_tablespace
build immediate
refresh complete on demand
as
select id,cid,type,trefid,ordrefid,amount,paydt, count(1) as cnt
from table1
group by id,cid,type,trefid,ordrefid,amount,paydt
having count(1) > 1;

-- dedup data (or put into procedure and schedule along with mat view refresh above)
declare
  -- make sure my_dups_mv is refreshed first
  cursor dup_cur is
  select * from my_dups_mv;

  type duprec_t is record(row_id rowid);
  duprec duprec_t;
  type duptab_t is table of duprec_t index by pls_integer;
  duptab duptab_t;

  l_ctr pls_integer := 0;
  l_dupcnt pls_integer := 0;
begin
  for rec in dup_cur
  loop
    l_ctr := l_ctr + 1;

    -- assuming needed indexes exist
    select rowid
    bulk collect into duptab
    from table1
    where id = rec.id
    and cid = rec.cid
    and type = rec.type
    and trefid = rec.trefid
    and ordrefid = rec.ordrefid
    and amount = rec.amount
    and paydt = rec.paydt
    -- order by whatever makes sense to make the "keeper" float to top
    order by loaddt desc
    ;

    for i in 2 .. duptab.count
    loop
      l_dupcnt := l_dupcnt + 1;
      delete from table1 where rowid = duptab(i).row_id;
    end loop;

    if (mod(l_ctr, 10000) = 0) then
      -- log to log table here (calling autonomous procedure you'll need to implement)
      insert_logtable('Table1 deletes', 'Commit reached, deleted ' || l_dupcnt || ' rows');
      commit;
    end if;

  end loop;
  commit;
end;

Проверьте свою таблицу журналов для статуса прогресса.

person tbone    schedule 01.10.2012
comment
Почему бы не forall i in duptab.first .. duptab.last (иначе я согласен, если это занимает 15 часов)? - person Ben; 01.10.2012
comment
@ Бен, эй, Бен, я удаляю со второго по последнее, оставляя первое. - person tbone; 02.10.2012

<сильный>1. Параллельно

alter session enable parallel dml;

DELETE /*+ PARALLEL */ FROM TABLE1 A WHERE loaddt < (
...

Предполагая, что у вас есть Enterprise Edition, разумная конфигурация сервера и вы используете 11g. Если вы не используете 11g, параллельный синтаксис немного отличается.

<сильный>2. Уменьшите требования к памяти

На плане показано хэш-соединение, что, вероятно, хорошо. Но без каких-либо полезных фильтров Oracle приходится хэшировать всю таблицу. (Запрос Tbone, который использует только GROUP BY, выглядит лучше и может работать быстрее. Но он также, вероятно, столкнется с той же проблемой, пытаясь отсортировать или хешировать всю таблицу.)

Если хэш не помещается в памяти, его необходимо записать на диск, что может быть очень медленным. Поскольку вы запускаете этот запрос каждую неделю, только одна из таблиц должна просматривать все строки. В зависимости от того, когда именно он запускается, вы можете добавить что-то вроде этого в конец запроса: ) where b.loaddt >= sysdate - 14. Это может значительно уменьшить количество операций записи во временное табличное пространство. И это также может уменьшить количество операций ввода-вывода при чтении, если вы используете какую-либо стратегию разбиения, например предложенную jakub.petr.

<сильный>3. Активный отчет

Если вы хотите точно знать, что делает ваш запрос, запустите Active Report:

select dbms_sqltune.report_sql_monitor(sql_id => 'YOUR_SQL_ID_HERE', type => 'active')
from dual;

(Сохраните результат в файле .html и откройте его в браузере.)

person Jon Heller    schedule 02.10.2012