Il modo migliore per eliminare un recordset molto grande in Oracle


18

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 davvero 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 alla 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 comprometta 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 deve essere al 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?


Nota anche che ci sono oltre 30 indici in gioco qui
jcolebrand

Risposte:


17

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;

Potrei aver semplificato troppo la logica alla base della determinazione dei record da eliminare, ma questa è un'idea molto interessante. Una cosa da considerare, tuttavia, è la performance quotidiana. Lo spurgo è "il nostro problema", il cliente non accetterà prestazioni degradate solo per risolverlo. Sembra, da alcuni dei commenti e dalla risposta di Gary, che questo potrebbe essere un problema con il partizionamento?
Coding Gorilla

Non sono sicuro che questa sia la risposta che stiamo cercando, ma questo è sicuramente un approccio molto interessante che esamineremo.
Codifica Gorilla,

14

La soluzione classica a questo è quella di partizionare le tabelle, ad esempio 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 UNIONdurante 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 (è analoga a aTRUNCATEin termini di carico perché è quello che stai realmente 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 automatizzare facilmente questo con a DBMS_JOB).

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


FWIW usiamo questa tecnica sul mio sito su un database di 30Tb +
Gaius

Il problema con il partizionamento è che non esiste un modo chiaro per partizionare i dati. In una delle due tabelle (non quella mostrata di seguito) i criteri utilizzati per eseguire l'eliminazione si basano su due campi data diversi (e distinti) e un campo stato. Ad esempio, se lo stato è Aquindi se ha DateApiù di 3 anni, viene eliminato. Se lo stato è Be ha DateBpiù di 10 anni, viene eliminato. Se la mia comprensione del partizionamento è corretta, il partizionamento non sarebbe utile in una situazione come questa (almeno per quanto riguarda lo spurgo).
Coding Gorilla

Puoi partizionare per stato e sottopartizione per intervallo di date. Ma se lo stato (o la data) cambia, effettua effettivamente un'eliminazione da una sotto-partizione e un inserimento nell'altra. In breve, puoi avere un successo sui processi di tutti i giorni per risparmiare tempo sulla tua pulizia.
Gary,

6
In alternativa, è possibile creare una colonna virtuale che mostri DateA quando lo stato è A e DateB quando lo stato è B e quindi partizionare sulla colonna virtuale. Si verificherebbe la stessa migrazione della partizione, ma aiuterebbe l'eliminazione. Sembra che questo sia già stato pubblicato come risposta.
Leigh Riffel,

4

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.


2

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 le tue esigenze. 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 notazione agli utenti. Facciamo 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.


Che dire del massiccio "annulla" e "ripeti" "elimina" genererà? Soffoca anche IO ... l'approccio basato sull'eliminazione dovrebbe certamente essere un NO .. NO per le tabelle di grandi dimensioni.
pahariayogi,

1

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, una volta terminato, (e testato), rinominare la tabella esistente my_table_olde rinominare la tabella "lavoro" in my_table. Una volta che ti senti a tuo agio con tutto drop my_table_old purgeper sbarazzarti del vecchio tavolo. Se sono presenti alcuni vincoli di chiave esterna, dai un'occhiata al dbms_redefinition pacchetto PL / SQL . Clonerà gli indici, i contrappunti, ecc. Quando si utilizzano le opzioni appropriate. Questa è la sintesi di un suggerimento di Tom Kyte di AskTomfama. 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
Questo può essere considerato se (a) l'eliminazione è un'attività una tantum. (b) se hai meno righe da conservare e la maggior parte dei dati da rimuovere ...
pahariayogi,

0

quando si rilascia 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 file al mese"

consiglierei di usare l'eliminazione batch di PL / SQL, credo che diverse ore siano ok.


1
Se si dispone di una chiave primaria, l'eliminazione di una partizione non dovrebbe rendere inutilizzabili gli indici globali. Ma se l'OP ha molti indici globali ci sarà un costo elevato per il rilascio delle partizioni. Nel caso ideale quando qualcuno esegue il partizionamento di una tabella, il partizionamento si basa sulla chiave primaria e non necessita di indici globali. Che ogni query è in grado di sfruttare la potatura delle partizioni.
Gandolf989,

@Gandolf989 lasciando cadere una partizione renderà sempre inutilizzabile un indice globale
miracle173
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.