оптимизиране на изявление за изтриване на дублиран 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 изтриване на dup преди това.   -  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%, тогава можете да помислите за индекс. Бихте ли могли по някакъв начин да разделите таблицата си - например седмица по седмица и след това да сканирате само действителната седмица?

Може би това ще бъде по-ефективно. Когато използвате агрегатна функция, тогава Oracle трябва да премине през всички съответни редове (във вашия случай пълно сканиране), но когато използвате съществува, той спира, когато бъде намерено първото срещане. (и разбира се, заявката би била много по-бърза, когато имаше един базиран на функция (заради 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

Всъщност няма такова нещо като „изображение“, съхранено в таблица на Access, а само двоичен поток. Следователно лявата страна на изражението ви не знае, че дясната страна предоставя изображение. Ще трябва да предавате двоичния поток в поток във VB.NET, след което да използвате методите на System.Graphics, за да го превърнете в BMP или PNG или каквото и да е друго. Можете да присвоите този обект на бутона.

- 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
Благодаря, jakub, това DELETE беше супер бързо! Току-що промених A.loaddt ‹ b.loaddt - person Ram; 04.10.2012

Въпреки че някои може да не са съгласни, аз съм привърженик на процедурно изпълнение на големи, продължителни изтривания. По мое мнение е много по-лесно да контролирате и проследявате напредъка (и вашият DBA ще ви харесва повече ;-) Освен това не съм сигурен защо трябва да присъединявате таблица1 към себе си, за да идентифицирате дубликати (и бих бил любопитен, ако някога се натъкнете на моментна снимка на твърде стари проблеми с настоящия ви подход). Също така не трябва да се нуждаете от множество изрази за изтриване, всички дубликати трябва да се обработват в един процес. И накрая, трябва да проверите ЗАЩО непрекъснато въвеждате дубликати всяка седмица и може би да промените процеса на зареждане (може би да направите сливане/подмяна, а не всички вмъквания).

Въпреки това може да опитате нещо като:

-- 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
@Бен, здравей, Бен, премахвам от 2-ро до последно, запазвайки първото. - 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. Това може значително да намали обема на запис във временното таблично пространство. И може също така да намали IO за четене, ако използвате някаква стратегия за разделяне, като предложената от jakub.petr.

3. Активен отчет

Ако искате да знаете точно какво прави вашата заявка, стартирайте активния отчет:

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

(Запазете резултата в .html файл и го отворете с браузър.)

person Jon Heller    schedule 02.10.2012