it-swarm-eu.dev

Nejlepší způsob, jak odstranit velmi velké sady záznamů v Oracle

Spravuji aplikaci, která má velmi velké (téměř 1 TB dat s více než 500 miliony řádků v jedné tabulce) databáze Oracle backend. Databáze opravdu nic nedělá (žádné SProcs, žádné triggery nebo nic), je to jen úložiště dat.

Každý měsíc musíme vyčistit záznamy ze dvou hlavních tabulek. Kritéria pro čištění se liší a je kombinací věku řádků a několika stavových polí. Obvykle skončíme očištěním mezi 10 a 50 miliony řádků za měsíc (prostřednictvím importu přidáváme asi 3-5 milionů řádků týdně).

V současné době musíme toto smazat v dávkách asi 50 000 řádků (tj. Smazat 50000, komit, smazat 50000, odevzdat, opakovat). Pokus o smazání celé dávky najednou způsobí, že databáze přestane reagovat přibližně jednu hodinu (v závislosti na počtu řádků). Odstranění řádků v dávkách, jako je tato, je v systému velmi drsné a obvykle to musíme udělat „podle času“ v průběhu týdne; umožnění nepřetržitého spouštění skriptu může mít za následek snížení výkonu, které je pro uživatele nepřijatelné.

Věřím, že tento druh dávkového mazání také snižuje výkonnost indexu a má další dopady, které nakonec způsobí zhoršení výkonu databáze. Na jedné tabulce je 34 indexů a velikost dat indexu je ve skutečnosti větší než samotná data.

Zde je skript, který jeden z našich IT lidí používá k tomuto očištění:

BEGIN
LOOP

delete FROM tbl_raw 
  where dist_event_date < to_date('[date]','mm/dd/yyyy') and rownum < 50000;

  exit when SQL%rowcount < 49999;

  commit;

END LOOP;

commit;

END;

Tato databáze musí být o 99,99999% a my máme pouze 2denní okno údržby jednou ročně.

Hledám lepší metodu pro odstranění těchto záznamů, ale zatím žádné nemám. Nějaké návrhy?

19
Coding Gorilla

Logika s 'A' a 'B' může být "skrytá" za sloupcem virtuální, ve kterém byste mohli udělat rozdělení:

alter session set nls_date_format = 'yyyy-mm-dd';
drop   table tq84_partitioned_table;

create table tq84_partitioned_table (
  status varchar2(1)          not null check (status in ('A', 'B')),
  date_a          date        not null,
  date_b          date        not null,
  date_too_old    date as
                       (  case status
                                 when 'A' then add_months(date_a, -7*12)
                                 when 'B' then            date_b
                                 end
                        ) virtual,
  data            varchar2(100) 
)
partition   by range  (date_too_old) 
( 
  partition p_before_2000_10 values less than (date '2000-10-01'),
  partition p_before_2000_11 values less than (date '2000-11-01'),
  partition p_before_2000_12 values less than (date '2000-12-01'),
  --
  partition p_before_2001_01 values less than (date '2001-01-01'),
  partition p_before_2001_02 values less than (date '2001-02-01'),
  partition p_before_2001_03 values less than (date '2001-03-01'),
  partition p_before_2001_04 values less than (date '2001-04-01'),
  partition p_before_2001_05 values less than (date '2001-05-01'),
  partition p_before_2001_06 values less than (date '2001-06-01'),
  -- and so on and so forth..
  partition p_ values less than (maxvalue)
);

insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '2008-04-14', date '2000-05-17', 
 'B and 2000-05-17 is older than 10 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '1999-09-19', date '2004-02-12', 
 'B and 2004-02-12 is younger than 10 yrs, must be kept');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2000-06-16', date '2010-01-01', 
 'A and 2000-06-16 is older than 3 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2009-06-09', date '1999-08-28', 
 'A and 2009-06-09 is younger than 3 yrs, must be kept');

select * from tq84_partitioned_table order by date_too_old;

-- drop partitions older than 10 or 3 years, respectively:

alter table tq84_partitioned_table drop partition p_before_2000_10;
alter table tq84_partitioned_table drop partition p_before_2000_11;
alter table tq84_partitioned_table drop partition p2000_12;

select * from tq84_partitioned_table order by date_too_old;
18

Klasickým řešením je partition vaše tabulky, např. za měsíc nebo za týden. Pokud jste se s nimi dosud nesetkali, je rozdělená tabulka jako několik identicky strukturovaných tabulek s implicitní UNION při výběru a Oracle automaticky uloží řádek do příslušného oddílu, když jej vložíte na základě kritérií rozdělení. Zmíníte indexy - každý oddíl má také své vlastní dělené indexy. Je to velmi levná operace v Oracle, že zrušíte diskový oddíl (je analogický s TRUNCATE, pokud jde o zatížení, protože to opravdu děláte - zkrácení nebo vypuštění jedné z těchto neviditelných dílčích tabulek). Bude to značné množství zpracování na rozdělení „po skutečnosti“, ale nemá smysl pláč nad rozlitým mlékem - výhody toho, že se tak daleko dosáhne, převáží náklady. Každý měsíc byste rozdělili horní oddíl a vytvořili nový oddíl pro data příštího měsíce (můžete snadno automatizovat pomocí DBMS_JOB).

A s oddíly můžete také využít paralelní dotaz a odstranění oddíl , což by mělo učinit vaše uživatele velmi šťastnými ...

14
Gaius

Jedním z aspektů, které je třeba zvážit, je, jak velká je výkonnost odstranění z indexů a kolik z hrubé tabulky. Každý záznam odstraněný z tabulky vyžaduje stejné odstranění řádku z každého indexu btree. Pokud máte 30+ btree indexů, mám podezření, že většinu času strávíte údržbou indexů.

To má vliv na užitečnost rozdělení. Řekněme, že máte index jména. Standardní Btree index, vše v jednom segmentu, bude možná muset udělat čtyři skoky, aby se dostal z kořenového bloku do bloku listů a pátého čtení, aby získal řádek. Pokud je tento index rozdělen do 50 segmentů a nemáte klíč oddílu jako součást dotazu, bude třeba zkontrolovat každý z těchto 50 segmentů. Každý segment bude menší, takže možná budete muset udělat pouze 2 skoky, ale přesto můžete skončit tím, že provedete 100 čtení namísto předchozích 5.

Pokud se jedná o indexy bitmap, jsou rovnice odlišné. Pravděpodobně nepoužíváte indexy k identifikaci jednotlivých řádků, ale spíše jejich sady. Takže spíše než dotaz pomocí 5 IO pro vrácení jednoho záznamu, to bylo pomocí 10 000 IO. Proto nebude na režii v dalších oddílech indexu záležet.

4
Gary

vymazání 50 milionů záznamů za měsíc v dávkách 50 000 je pouze 1 000 iterací. pokud provedete 1 smazání každých 30 minut, mělo by to vyhovovat vašim požadavkům. naplánovaná úloha pro spuštění zadaného dotazu, ale odebrání smyčky, takže se provede pouze jednou, by uživatelům neměla způsobit znatelné odstranění obsahu. Děláme stejný objem záznamů v našem výrobním závodě, který běží téměř 24/7 a odpovídá našim potřebám. Vlastně jsme ji rozložili o dalších 10 000 záznamů každých 10 minut, což se spustí za asi 1 nebo 2 sekundy spuštěné na našich unixových serverech Oracle.

2
Jason Jakob

Pokud místo na disku nemá prémii, můžete vytvořit pracovní kopii tabulky, řekněme my_table_new, pomocí CTAS (Create Table As Select) s kritérii, která vynechají záznamy, které mají být zrušeny. Můžete vytvořit příkaz pro vytvoření paralelně as připojením nápovědy, aby byl rychlý a poté vytvořte všechny své indexy. Po dokončení (a testování) přejmenujte stávající tabulku na my_table_old a přejmenujte tabulku „work“ na my_table. Jakmile budete se vším všudy drop my_table_old purge zbavit se starého stolu. Pokud existuje spousta omezení cizího klíče, podívejte se na dbms_redefinitionPL/SQL balíček . Při použití příslušných voleb bude klonovat vaše indexy, kontrastu atd. Toto je shrnutí návrhu Toma Kyteho na AskTom slávu. Po prvním spuštění můžete vše zautomatizovat a vytvořit tabulku by mělo jít mnohem rychleji, a může být provedeno, když je systém v provozu, a prostoje aplikace by byly omezeny na méně než minutu na provedení přejmenování tabulek. Použití CTAS bude mnohem rychlejší než provádění několika dávkových mazání. Tento přístup může být užitečný zejména v případě, že nemáte licenci pro vytváření oddílů.

Ukázka CTAS, vedení řádků s údaji za posledních 365 dní a flag_inactive = 'N':

create /*+ append */ table my_table_new 
   tablespace data as
   select /*+ parallel */ * from my_table 
       where some_date >= sysdate -365 
       and flag_inactive = 'N';

-- test out my_table_new. then if all is well:

alter table my_table rename to my_table_old;
alter table my_table_new rename to my_table;
-- test some more
drop table my_table_old purge;
1
Mark Stewart

při vypouštění oddílu necháte globální indexy nepoužitelné, které je třeba znovu vytvořit, znovu vytvořit globální indexy by byl velký problém, jako kdybyste to dělali online, bude to docela pomalé, jinak budete potřebovat prostoje. v obou případech se tento požadavek nehodí.

"Obvykle skončíme očištěním mezi 10 a 50 miliony řádků za měsíc"

doporučil bych používat PL/SQL dávkové mazání, myslím, že několik hodin je v pořádku.

0
iceburge5