it-swarm.it

Il modo migliore per eliminare un recordset molto grande in Oracle

Gestisco un'applicazione che ha un back-end di database Oracle molto grande (quasi 1 TB di dati con oltre 500 milioni di righe in una tabella). Il database non fa veramente nulla (niente SProcs, niente trigger o altro) è solo un archivio dati.

Ogni mese ci viene richiesto di eliminare i record dalle due tabelle principali. I criteri per l'eliminazione variano ed è una combinazione di età delle righe e un paio di campi di stato. In genere finiamo per eliminare tra 10 e 50 milioni di righe al mese (aggiungiamo circa 3-5 milioni di righe a settimana tramite le importazioni).

Attualmente è necessario eseguire questa eliminazione in batch di circa 50.000 righe (ovvero eliminare 50000, comit, eliminare 50000, commit, ripetere). Il tentativo di eliminare l'intero batch in una sola volta rende il database non rispondente per circa un'ora (a seconda del numero di righe). L'eliminazione delle righe in lotti come questo è molto approssimativa sul sistema e in genere dobbiamo farlo "come il tempo lo permette" nel corso di una settimana; consentire l'esecuzione continua dello script può comportare un peggioramento delle prestazioni inaccettabile per l'utente.

Ritengo che questo tipo di eliminazione in batch degrada anche le prestazioni dell'indice e abbia altri impatti che alla fine possono compromettere le prestazioni del database. Esistono 34 indici su una sola tabella e la dimensione dei dati dell'indice è in realtà maggiore dei dati stessi.

Ecco lo script utilizzato da una delle nostre persone IT per eseguire questa eliminazione:

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;

Questo database must deve essere aumentato del 99.99999% e abbiamo una finestra di manutenzione di 2 giorni una volta all'anno.

Sto cercando un metodo migliore per rimuovere questi record, ma devo ancora trovarne uno. Eventuali suggerimenti?

19
Coding Gorilla

La logica con 'A' e 'B' potrebbe essere "nascosta" dietro una colonna virtuale su cui è possibile eseguire il partizionamento:

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

La soluzione classica a questo è partizione le tue tabelle, ad es. per mese o per settimana. Se non li hai mai trovati prima, una tabella partizionata è come diverse tabelle strutturate in modo identico con un implicito UNION durante la selezione e Oracle memorizzerà automaticamente una riga nella partizione appropriata quando la inserisce in base ai criteri di partizionamento. Tu citi gli indici - bene ogni partizione ottiene anche i suoi indici partizionati. È un'operazione molto economica in Oracle eliminare una partizione (è analogo a un TRUNCATE in termini di carico perché è proprio quello che stai facendo - troncare o far cadere una di queste sotto-tabelle invisibili). Sarà una notevole quantità di elaborazione da suddividere "dopo il fatto", ma non ha senso piangere sul latte versato: i vantaggi di fare finora superano i costi. Ogni mese divideresti la partizione superiore per creare una nuova partizione per i dati del mese successivo (puoi facilmente automatizzare questi con un DBMS_JOB).

E con le partizioni puoi anche sfruttare query parallela e eliminazione delle partizioni , che dovrebbe rendere molto felici i tuoi utenti ...

14
Gaius

Un aspetto da considerare è la quantità delle prestazioni di eliminazione risultante dagli indici e quanto dalla tabella non elaborata. Ogni record cancellato dalla tabella richiede la stessa cancellazione della riga da ogni indice btree. Se hai oltre 30 indici btree, sospetto che la maggior parte del tuo tempo sia dedicato alla manutenzione degli indici.

Ciò ha un impatto sull'utilità del partizionamento. Supponi di avere un indice sul nome. Un indice Btree standard, tutto in un segmento, potrebbe dover fare quattro salti per passare dal blocco radice al blocco foglia e una quinta lettura per ottenere la riga. Se tale indice è partizionato in 50 segmenti e non si dispone della chiave di partizione come parte della query, sarà necessario verificare ciascuno di quei 50 segmenti. Ogni segmento sarà più piccolo, quindi potresti dover fare solo 2 salti ma potresti comunque finire per fare 100 letture anziché le 5 precedenti.

Se sono indici bitmap, le equazioni sono diverse. Probabilmente non stai usando gli indici per identificare le singole righe, ma piuttosto gruppi di esse. Quindi, anziché una query che utilizzava 5 IO per restituire un singolo record, utilizzava 10.000 IO. Pertanto, l'overhead aggiuntivo in partizioni extra per l'indice non avrà importanza.

4
Gary

la cancellazione di 50 milioni di record al mese in lotti di 50.000 è solo 1000 iterazioni. se lo fai 1 elimini ogni 30 minuti dovrebbe soddisfare i tuoi requisiti. un'attività pianificata per eseguire la query che hai postato ma rimuovere il ciclo in modo che venga eseguita una sola volta non dovrebbe causare una degna notorietà agli utenti. Facciamo circa lo stesso volume di record nel nostro stabilimento di produzione che funziona praticamente 24 ore su 24, 7 giorni su 7 e soddisfa le nostre esigenze. In realtà lo abbiamo distribuito un po 'più di 10.000 record ogni 10 minuti, che viene eseguito in circa 1 o 2 secondi in esecuzione sui nostri server Oracle unix.

2
Jason Jakob

Se lo spazio su disco non è un premio, potresti essere in grado di creare una copia "di lavoro" della tabella, ad esempio my_table_new, utilizzando CTAS (Crea tabella come selezione) con criteri che ometterebbero i record da eliminare. È possibile eseguire l'istruzione create in parallelo e con il suggerimento append per renderla veloce, quindi creare tutti gli indici. Quindi, al termine, (e testato), rinominare la tabella esistente in my_table_old e rinomina la tabella "lavoro" in my_table. Una volta che ti senti a tuo agio con tutto drop my_table_old purge per sbarazzarsi del vecchio tavolo. Se sono presenti alcuni vincoli di chiave esterna, dai un'occhiata a dbms_redefinitionpacchetto PL/SQL . Clonerà gli indici, i contrappunti, ecc. Quando si utilizzano le opzioni appropriate. Questa è la sintesi di un suggerimento di Tom Kyte di AskTom fama. Dopo la prima esecuzione, è possibile automatizzare tutto e la tabella di creazione dovrebbe andare molto più veloce e può essere eseguita mentre il sistema è attivo e il tempo di inattività dell'applicazione sarebbe limitato a meno di un minuto per la ridenominazione delle tabelle. L'utilizzo di CTAS sarà molto più rapido rispetto all'eliminazione di più batch. Questo approccio può essere particolarmente utile se non si dispone di licenze di partizionamento.

Esempio di CTAS, mantenendo le righe con i dati degli ultimi 365 giorni e 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

quando si elimina una partizione, si rendono inutilizzabili gli indici globali, che è necessario ricostruire, la ricostruzione degli indici globali sarebbe un grosso problema, come se lo si fa online, sarà piuttosto lento, altrimenti è necessario il tempo di inattività. in entrambi i casi, non può adattarsi al requisito.

"In genere finiamo per eliminare tra 10 e 50 milioni di righe al mese"

mi consiglia di utilizzare l'eliminazione batch PL/SQL, diverse ore è ok, penso.

0
iceburge5