martedì 1 gennaio 2013

Recyclebin di Oracle


A partire dalla versione 10g, Oracle ha implementato il recyclebin, che non è altro che il cestino.
Chi non ha mai sbrasato una tabella fondamentale del suo schema, piena zeppa di dati più che fondamentali, perdendo diversi anni di vita nello sgomento, pensando a come insabbiare il tutto o scaricare immediatamente la responsabilità su qualche meccanismo di backup/ripristino andato a male? evidentemente alla Oracle qualcuno ha fatto presente questo scenario, e quindi si sono inventati il recyclebin, e quindi tanto di cappello, considerando che parliamo di database.
Ad ogni modo, chissà come, chissà quando, chissà quale algoritmo lunare avranno inventato, ma guarda che strano cancello roba e non mi si libera lo storage, ma che problema ci sarà, chi sia stato non si sa.... ed ecco svelato ilcano. Il recyclebin rinomina gli oggetti cancellati, loggandosi ovviamente la cancellazione e togliendoli dalle consuete viste di sistema che listano oggetti.

Piccola digressione: non ho ancora capito se una tabella in oracle si può rinominare o no. diversi tool di gestione database consentono la rinomina, ma di fatto dietro le quinte creano una nuova tabella, ci travasano i dati e cancellano la vecchia, il che potrebbe anche fare incazzare più di qualcuno, tra indici, statistiche, ottimizzazioni e cavoli a merenda.

Ad ogni modo, strumento utilissimo che vi salva appunto le terga nel momento in cui cancellate per sbaglio una oggetto. Attenzione, parliamo di oggetti, non di dati, per cui tutto questo discorso vale solo se cancellate ad esempio un intera tabella, non certo se fate un bel DELETE FROM, da quello non vi salva certo il recyclebin ma dovete affidarvi ai meccanismi di rollback, flashback, flash gordon e capitan america che ritenete più opportuni, ammesso che li abbiate mai attivati sul vostro db, visto che su Oracle qualsiasi virgola muovi serve un team di architetti di sistema con almeno 3 anni di servizio sulla USS Enterprise.

Per cui, caro amico, se hai fatto DELETE o TRUNCATE sui dati e speravi di aver trovato ciò che ti avrebbe salvato, una pacca sulla spalla non te la leva nessuno, puoi riprendere a googlare e a sudare freddo.

Vediamo Come funziona


Ci sono due viste di RB(chiameremo RB il recyclebin): USER_RECYCLEBIN e DBA_RECYCLEBIN. 
Di defaut, se leggete RECYCLEBIN, state leggendo USER_RECYCLEBIN. Sempre di default, il RB
è abilitato di default dalla 10g, ma si può disabilitare dal parametro RECYCLEBIN a livello di sistema o di sessione.
Come dicevamo, quando droppiamo un oggetto, questo viene rinominato, e ovviamente tutti gli altri oggetti eliminati a cascata(indici, chiavi, trigger, ecc), con un nome che comincia con BIN$.

Vediamo un esempio, creiamo una tabella con un record di prova
SQL> create table tst (col varchar2(10), row_chng_dt date);
 
Table created.
 
SQL> insert into tst values ('Version1', sysdate);
 
1 row created.
 
SQL> select * from tst ;
 
COL        ROW_CHNG
---------- --------
Version1   16:10:03
Se il RB è attivo, droppando la tabella, la ritroveremo nella vista RECYCLEBIN:
SQL> drop table tst;
 
Table dropped.
 
SQL> select object_name, original_name, type, can_undrop as "UND", can_purge as "PUR", droptime
  2  from recyclebin 
SQL> /
 
OBJECT_NAME                    ORIGINAL_NAME TYPE  UND PUR DROPTIME
------------------------------ ------------- ----- --- -------------------
BIN$HGnc55/7rRPgQPeM/qQoRw==$0 TST           TABLE YES YES 2006-09-01:16:10:12

Da notare che i dati sono ancora in tabella e possono essere recuperati con una normale query, ovviamente usando il nuovo nome assegnato alla tabella:
SQL> alter session set nls_date_format='HH24:MI:SS' ;
 
Session altered.
 
SQL> select * from "BIN$HGnc55/7rRPgQPeM/qQoRw==$0" ;
 
COL        ROW_CHNG
---------- --------
Version1   16:10:03

Proprio per questo motivo è possibile tornare indietro e annullare il drop, facendo un "flashback drop". Il comando è FLASHBACK TABLE… TO BEFORE DROP, e come scaltramente starete pensando, non fa altro che riportare la tabella al nome originale :
SQL> flashback table tst to before drop;
 
Flashback complete.
 
SQL> select * from tst ;
 
COL        ROW_CHNG
---------- --------
Version1   16:10:03
 
SQL> select * from recyclebin ;
 
no rows selected 
 Proprio per questo se droppiamo una tabella anche di dimensioni sensibili non vedremo liberarsi spazio nel tablespace. Di fatto i dati non si sono mossi da dov'erano, e non lo faranno finchè non decideremo di eliminare definitivamente la tabella dal nostro RB. Per farlo basta il comando PURGE. Vedete nell'esempio come dopo il purge l'oggetto non compaia piu nel RB, il che vuol dire che è stato definitivamente eliminato, e quindi non più ripristinabile. 
SQL> select object_name, original_name, type, can_undrop as "UND", can_purge as "PUR", droptime
  2  from recyclebin
SQL> /
 
OBJECT_NAME                    ORIGINAL_NAME TYPE                      UND PUR DROPTIME
------------------------------ ------------- ------------------------- --- --- -------------------
BIN$HGnc55/7rRPgQPeM/qQoRw==$0 TST           TABLE                     YES YES 2006-09-01:16:10:12
 
SQL> purge table "BIN$HGnc55/7rRPgQPeM/qQoRw==$0" ;
 
Table purged.
 
SQL> select * from recyclebin ;
 
no rows selected

Se vi siete sbagliati pure qua, lasciate stare, o si è fatto veramente troppo tardi, o dovevate andare a letto prima, oppure avete il cervello bruciato da qualche acido importante.
Ma proseguiamo...

Ci sono vari modi di fare il purge. facendo PURGE RECYCLEBIN si svuota il recyclebin dell'utente (ricordate il sinonimo RECYCLEBIN corrisponde a USER_RECYCLEBIN), mentre se siamo dba possiamo fare PURGE DBA_RECYCLEBIN e cancellare tutto il cancellabile, in un impeto di megalomania.

Se non sappiamo o non possiamo disattivare il recyclebin, ma abbiamo comunque bisogno di sfruttare al massimo il nostro tablespace o la nostra quota utente, il RB ci viene in aiuto, per quello che può. In sostanza, quando abbiamo finito lo spazio, il RB comincia a cancellare gli oggetti a partire dai meno recenti, quel tanto che basta per far spazio ai nuovi oggetti e dati che stiamo scrivendo. Se il nostro tablespace è AUTOEXTEND ON, ovvero si autoestende, prima di estendere un datafile il RB cancellerà degli oggetti. Il che vuol dire che l'ingombro del cestino non influirà sulla larghezza massima del tablespace, anche se è autoestendibile.


...e se cancello più volte una tabella con lo stesso nome?

A parte il fatto che probabilmente se lo stai facendo sei un po confuso, proviamo a farlo:
SQL> create table tst (col varchar2(10), row_chng_dt date);
 
Table created.
 
SQL> insert into tst values ('Version1', sysdate);
 
1 row created.
 
SQL> drop table tst;
 
Table dropped.
 
SQL> create table tst (col varchar2(10), row_chng_dt date);
 
Table created.
 
SQL> insert into tst values ('Version2', sysdate);
 
1 row created.
 
SQL> drop table tst;
 
Table dropped.
 
SQL> select object_name, original_name, type, can_undrop as "UND", can_purge as "PUR", droptime
  2  from recyclebin;
 
OBJECT_NAME                    ORIGINAL_NAME TYPE  UND PUR DROPTIME
------------------------------ ------------- ----- --- --- -------------------
BIN$HGnc55/7rRPgQPeM/qQoRw==$0 TST           TABLE YES YES 2006-09-01:16:10:12
BIN$HGnc55/8rRPgQPeM/qQoRw==$0 TST           TABLE YES YES 2006-09-01:16:19:53
Query the two dropped tables to verify that they are different:
 
SQL> select * from "BIN$HGnc55/7rRPgQPeM/qQoRw==$0";
 
COL        ROW_CHNG
---------- --------
Version1   16:10:03
 
SQL> select * from "BIN$HGnc55/8rRPgQPeM/qQoRw==$0" ;
 
COL        ROW_CHNG
---------- --------
Version2   16:19:45

Come vedete dai risultati interrogando il RB otteniamo due tabelle droppate, a orari differenti ovviamente. Se facciamo un FLASHBACK DROP per la tabella TST, quale versione ripristinerà Oracle?
SQL> flashback table tst to before drop;
 
Flashback complete.
 
SQL> select * from tst;
 
COL        ROW_CHNG
---------- --------
Version2   16:19:45

Oracle ripristinerà sempre la versione più recente dell'oggetto droppato.
Per ripristinare una versione meno recente dell'ultima droppata, possiamo rilanciare il comando di flashback drop finchè non otteniamo la versione che ci interessa, oppure possiamo lanciare il comando sulla versione esatta da ripristinare. Droppiamo di nuovo due volte la tabella e ne ripristiniamo dal RB la versione meno recente, chiamandola per nome (occhio cambia solo /7 e /9 tra i nomi delle due versioni):
SQL> drop table tst;
 
Table dropped.
 
SQL> select object_name, original_name, type, can_undrop as "UND", can_purge as "PUR", droptime
  2  from recyclebin;
 
OBJECT_NAME                    ORIGINAL_NAME TYPE   UND PUR DROPTIME
------------------------------ ------------- ------ --- --- -------------------
BIN$HGnc55/7rRPgQPeM/qQoRw==$0 TST           TABLE  YES YES 2006-09-01:16:10:12
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST           TABLE  YES YES 2006-09-01:16:21:00
To flashback to the first version, refer to the BIN$... name of the first version of TST:
 
SQL> flashback table "BIN$HGnc55/7rRPgQPeM/qQoRw==$0" to before drop;
 
Flashback complete.
 
SQL> select * from tst;
 
COL        ROW_CHNG
---------- --------
Version1   16:10:03
Cosi facendo la versione meno recente è stata ripristinata in TST e non è più presente nel RB, mentre è invece presente la seconda tabella droppata, che non abbiamo flashbackato.
SQL> select object_name, original_name, operation, can_undrop as "UND", can_purge as "PUR", droptime
  2  from recyclebin;
 
OBJECT_NAME                    ORIGINAL_NAME  OPERATION UND PUR DROPTIME
------------------------------ -------------- --------- --- --- -------------------
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST            DROP      YES YES 2006-09-01:16:21:00
Oggetti Dipendenti

Ovviamente droppando una tabella, droppiamo anche tutti gli oggetti dipendenti da essa(indici, constraints, triggers, ecc). Con il RB abilitato, droppando la tabella, Oracle la rinomina insieme a tutti gli oggetti correlati, mantenendo intatte le relazioni tra essi. I trigger e gli indici ad esempio vengono modificati per puntare al nuovo nome della tabella droppata (quello che comincia con BIN$), mentre le stored procedure verranno invalidate
Ad esempio, aggiungiamo un indice, droppiamo la tabella e vediamo cosa troviamo nel cestino:
SQL> truncate table tst;
 
Table truncated.
 
SQL> insert into tst values ('Version3', sysdate);
 
1 row created.
 
SQL> create index ind_tst_col on tst(col);
 
Index created.
 
SQL> select * from tst;
 
COL        ROW_CHNG
---------- --------
Version3   16:26:10
 
SQL> drop table tst ;
 
Table dropped.
 
SQL> select object_name, original_name, type, can_undrop as "UND", can_purge as "PUR", droptime
  2  from recyclebin
  3  order by droptime;
 
OBJECT_NAME                    ORIGINAL_NAME  TYPE   UND PUR DROPTIME
------------------------------ -------------- ------ --- --- -------------------
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST            TABLE  YES YES 2006-09-01:16:21:00
BIN$HGnc55//rRPgQPeM/qQoRw==$0 TST            TABLE  YES YES 2006-09-01:16:27:36
BIN$HGnc55/+rRPgQPeM/qQoRw==$0 IND_TST_COL    INDEX  NO  YES 2006-09-01:16:27:36
The RECYCLEBIN views have a few other columns that make the relationship between TST and IND_TST_COL clear:
 
SQL> select object_name, original_name, type, can_undrop as "UND", 
  2  can_purge as "PUR", droptime, base_object, purge_object
  3  from recyclebin
  4  order by droptime;
 
OBJECT_NAME                    ORIGINAL_NAME   TYPE  UND PUR DROPTIME            BASE_OBJECT   PURGE_OBJECT
------------------------------ --------------- ----- --- --- ------------------- -----------   ------------
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST             TABLE  YES YES 2006-09-01:16:21:00 233032        233032
BIN$HGnc55//rRPgQPeM/qQoRw==$0 TST             TABLE  YES YES 2006-09-01:16:27:36 233031        233031
BIN$HGnc55/+rRPgQPeM/qQoRw==$0 IND_TST_COL     INDEX  NO  YES 2006-09-01:16:27:36 233031        233434
Troviamo quindi nel RB anche l'indice questa volta, rinominato. Se flashbackiamo la nostra tabella TST, noteremo che anche l'indice viene ripristinato, ma Oracle non lo rinomina col nome originale, e lo lascia col nome che aveva nel RB, ovvero BIN$xxx, come possiamo vedere:
SQL> flashback table tst to before drop;
 
Flashback complete.
 
SQL> select * from tst ;
 
COL        ROW_CHNG
---------- --------
Version3   16:26:10
 
SQL> select index_name from user_indexes where table_name='TST' ;
 
INDEX_NAME
------------------------------
BIN$HGnc55/+rRPgQPeM/qQoRw==$0
Se droppiamo di nuovo la tabella TST, l'indice verrà droppato con il nome originale cambiato, ovvero quello con cui era nominato nel RB quando era stato già droppato in precedenza, e già qui abbiamo fatto un bel casotto, ma ripeto siamo sempre nella zona dormitopoco/ètardi/acidi, per cui è giusto anche cosi:
SQL> drop table tst;
 
Table dropped.
 
SQL> select object_name, original_name, type, can_undrop as "UND", can_purge as "PUR", 
  2  droptime, base_object, purge_object
  3  from recyclebin
  4  order by droptime;
 
OBJECT_NAME                    ORIGINAL_NAME   TYPE  UND PUR DROPTIME            BASE_OBJECT PURGE_OBJECT
------------------------------ --------------- ----- --- --- ------------------- ----------- ------------
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST             TABLE YES YES 2006-09-01:16:21:00      233032       233032
BIN$HGnc56ABrRPgQPeM/qQoRw==$0 TST             TABLE YES YES 2006-09-01:16:31:43      233031       233031
BIN$HGnc56AArRPgQPeM/qQoRw==$1 BIN$HGnc55/+rRP INDEX NO  YES 2006-09-01:16:31:43      233031       233434
                               gQPeM/qQoRw==$0

Notate il campo CAN_UNDROP e CAN_PURGE, riportarti per praticità come UND E PUR. Un indice  non può essere flashbackato senza la sua tabella, per cui non lo si può flashbackare se non flashbackando la sua tabella. Se ne può invece fare il purge separatamente dalla tabella.
Vediamo come facendo il purge del solo indice e ripristinando la tabella, non troviamo più l'indice:
SQL> purge index "BIN$HGnc56AArRPgQPeM/qQoRw==$1" ;
 
Index purged.
 
SQL> select object_name, original_name, type, can_undrop as "UND", can_purge as "PUR", 
  2  droptime, base_object, purge_object
  3  from recyclebin
  4  order by droptime;
 
OBJECT_NAME                    ORIGINAL_NAME  TYPE  UND PUR DROPTIME            BASE_OBJECT PURGE_OBJECT
------------------------------ -------------- ----- --- --- ------------------- ----------- ------------
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST            TABLE YES YES 2006-09-01:16:21:00      233032       233032
BIN$HGnc56ABrRPgQPeM/qQoRw==$0 TST            TABLE YES YES 2006-09-01:16:31:43      233031       233031
Now, if we restore the table, it will be restored without the index:
 
SQL> flashback table tst to before drop;
 
Flashback complete.
 
SQL> select * from tst ;
 
COL        ROW_CHNG
---------- --------
Version3   16:26:10
 
SQL> select index_name from user_indexes where table_name='TST' ;
 
no rows selected
Per quanto riguarda i segmenti LOB, quelli creati ad esempio con i campi BLOB E CLOB, il procedimento del RB è sempre lo stesso, salvo che non se ne può fare il purge singolarmente. Sono ripristinati e cancellati sempre e solo in seguito al flashback o al purge della tabella di appartenenza.

Limitazioni

Ci sono delle limitazioni da tener presente, smanettando con l'RB.
Gli Indici Bitmap non finiscono nel RB, per cui se li avete droppati sono cancellati, e se ripristinate la tabella di origine non ve li ritroverete.
Stessa cosa per i logs delle viste materializzate, e per le foreign keys verso altre tabelle, quando droppate la tabella ve le perdete.

Se per problemi di spazio il RB comincia a fare il purge degli oggetti più vecchi, a parità di posizione, droppa prima gli indici. Ovviamente se ripristinate una tabella i cui indici sono stati droppati, verrà ripristinata senza indici...

Disabilitare il Recyblebin

Per non usare il RB in una singola drop, basta specificare l'opzione PURGE alla fine del drop, e l'oggetto verrà definitivamente cancellato senza passare dal RB:
SQL> purge recyclebin;
 
Recyclebin purged.
 
SQL> select * from recyclebin;
 
no rows selected
 
SQL> create table my_new_table (dummy varchar2(1));
 
Table created.
 
SQL> drop table my_new_table purge;
 
Table dropped.
 
SQL> select * from recyclebin;
 
no rows selected

Se disabilitiamo il RB a livello di sessione, con ALTER SESSION SET RECYCLEBIN=OFF, è la stessa cosa di fare ciascun drop mettendo il PURGE alla fine, per l'intera sessione o finchè non lo riattiviamo. Nulla ci impedisce comunque di ripristinare oggetti messi nel RB prima che lo disabilitassimo.

In conclusione...

Che dire, sicuramente il recyclebin è uno strumento utile che ci mette una rete di sicurezza in più ai casini che possiamo fare nella fretta o nella disattenzione del momento. E' anche vero che ripristinando tabelle con indici e trigger dovremo ripassare a rinominare, ma almeno i dati sono salvi, ed è quello l'importante.
Certo è che, come i classici avvisi di conferma di cancellazione, se clicchiamo più volte senza leggere non c'è sistema che ci possa salvare se non li backup, quando c'è e quando è possibile ripristinarlo. 
Mi è capitato di droppare l'intero schema SYSTEM di un cliente una volta, semplicemente per aver inavvertitamente scrollato con la rotella del mouse sul selettore delle connessioni del tool che utilizzavo per gestire il db, e quando me ne sono accorto ci mancava giusto mezzo schema di roba, per cui... parlo con cognizione di causa ;)




Nessun commento:

Posta un commento