Database corrotti, recupero tramite DBCC WRITEPAGES

Ciao a tutti!

Oggi si fa sul serio!

Con l'articolo odierno vorrei mostrarvi come recuperare un errore forzando il puntamento delle pagine.
Avremo così l'occasione di introdurre gli ultimi due comandi da tenere nella vostra cassetta degli attrezzi che sono il DBCC IND ed il DBCC WRITEPAGE

Un Avvertimento però: mai provare su un database di cui non si abbia un backup, il rischio è di far danni seri!

Buona lettura!


Database corrotti e DBCC WRITEPAGES


Supponete anche anche oggi vi siate trovati tra le mani un database corrotto.
Subito vi siete collegati ed avete eseguito il comando DBCC CHECKDB.

Il log vi mostra l'errore. La situazione che vi si presenta di fronte è questa:


...
Per l'oggetto "sys.wpr_bucket_table"sono disponibili 0 righe in 0 pagine.
Messaggio 8939, livello 16, stato 98, riga 28
Errore di tabella: ID di oggetto 581577110, ID di indice 1, ID di partizione 72057594043170816, ID di unità di allocazione 72057594049658880 (tipo In-row data), pagina (1:264). Test (IS_OFF (BUF_IOERR, pBUF->bstat)) non riuscito. Valori: 2057 e -4.
Messaggio 8928, livello 16, stato 1, riga 28
ID di oggetto 581577110, ID di indice 1, ID di partizione 72057594043170816, ID di unità di allocazione 72057594049658880 (tipo In-row data): non è possibile elaborare la pagina (1:264). Per altre informazioni, vedere gli altri errori.
Messaggio 8980, livello 16, stato 1, riga 28
Errore di tabella: ID di oggetto 581577110, ID di indice 1, ID di partizione 72057594043170816, ID di unità di allocazione 72057594049658880 (tipo In-row data). La pagina nodo dell'indice (1:265), slot 0 fa riferimento alla pagina figlio (1:264) e all'elemento figlio precedente (0:0), che non sono stati individuati.
Messaggio 8978, livello 16, stato 1, riga 28
Errore di tabella: ID di oggetto 581577110, ID di indice 1, ID di partizione 72057594043170816, ID di unità di allocazione 72057594049658880 (tipo In-row data). Nella pagina (1:266) manca un riferimento dalla pagina precedente (1:264). Possibile problema nel collegamento a catena.
Risultati DBCC per 'Elenco'.
Per l'oggetto "Elenco"sono disponibili 583 righe in 2 pagine.
CHECKDB ha trovato 0 errori di allocazione e 4 errori di coerenza nella tabella 'Elenco' (ID oggetto 581577110).
Risultati DBCC per 'sys.queue_messages_1977058079'.
Per l'oggetto "sys.queue_messages_1977058079"sono disponibili 0 righe in 0 pagine.
Risultati DBCC per 'sys.queue_messages_2009058193'.
Per l'oggetto "sys.queue_messages_2009058193"sono disponibili 0 righe in 0 pagine.
Risultati DBCC per 'sys.queue_messages_2041058307'.
Per l'oggetto "sys.queue_messages_2041058307"sono disponibili 0 righe in 0 pagine.
Risultati DBCC per 'sys.filestream_tombstone_2073058421'.
Per l'oggetto "sys.filestream_tombstone_2073058421"sono disponibili 0 righe in 0 pagine.
Risultati DBCC per 'sys.syscommittab'.
Per l'oggetto "sys.syscommittab"sono disponibili 0 righe in 0 pagine.
Risultati DBCC per 'sys.filetable_updates_2105058535'.
Per l'oggetto "sys.filetable_updates_2105058535"sono disponibili 0 righe in 0 pagine.
CHECKDB ha trovato 0 errori di allocazione e 4 errori di coerenza nel database 'DB_Corrotto'.
repair_allow_data_loss è il livello di correzione minimo per gli errori rilevati da DBCC CHECKDB (DB_Corrotto ).
Esecuzione DBCC completata. Se sono stati visualizzati messaggi di errore DBCC, rivolgersi all'amministratore di sistema.



Proviamo di capirci qualcosa!
Innanzitutto è essenziale leggere la parte che il SQL Server manager restutuisce in rosso.

Dunque dunque dunque...

Intanto la prima informazione che è il problema è su una tabella che si chiama Elenco.

Focalizziamoci poi sulla ultima parte della scritta in rosso.
Qua ci viene in aiuto la scritta "Possibile problema nel collegamento a catena":


72057594049658880 (tipo In-row data). Nella pagina (1:266) manca un riferimento dalla pagina precedente (1:264). Possibile problema nel collegamento a catena.
Risultati DBCC per 'Elenco'.


Cosa facciamo?

Innanzitutto apriamo la nostra scatola degli attrezzi ed mettiamoci dentro un nuovo comando che si chiama DBCC IND.

Cos'è il DBCC IND? E' comando restituisce l'elenco delle pagine di cui è composta una tabella.
Proviamo allora subito scrivendo:


DBCC IND(0,'ELENCO',1);


Ecco cosa restituisce:




 

Apriamo una piccola parentesi per capire meglio come è strutturata fisicamente una tabella:

  • La prima riga è la pagina IAM (pagetype=10)
  • La terza riga è l'indice cluster sulla tabella (pagetype=2)
  • Tutte le altre sono le pagine che contengno i nostri dati (pagetype=1)


Se ora guardate con attenzione dovreste vedere il problema.

Ma certo!

La pagina 264 punta attraverso il campo NextPageId alla pagina 266
Se osserviamo però il campo PrevPageId vediamo che contiene il valore 264!
No, questo è l'errore: se è la prima pagina di dati questo valore deve essere 0!



Sistemiamo l'errore

E adesso? come facciamo a sistemare questo errore?

In nostro soccorso arriva l'ultimo comando (non documentato), da mettere sempre nella nostra cassetta degli attrezzi, che è il comando DBCC WRITEPAGE.

E un comando potentissimo perchè ci permette di scrivere direttamente dentro al database! WOW!

Ma come funziona?

Bhe per usarlo ci servono un po di informazioni.

Partiamo dal nostro caso. Io ho scritto:


DBCC WRITEPAGE ('DB_CORROTTO', 1, 264, 8, 3, 0x000000,1)

Alcuni parametri li riconoscete sicuramente.
  • Il primo è il nome del database.
  • Il secondo ed il terzo sono il fileId ed il PageId che devo variare. (quindi 1:264)
Ora andiamo un po di più nello specifico:

Il quarto parametro è l'offset (8) ed il quinto il numero di caratteri che andremo a scrivere (3)

Bisogna sapere che ogni pagina ha una dimensione pari a 8192 byte di cui i primi 96 sono l'header, Qui, alla posizione 8 si trova appunto il campo PrevPageId.

Il sesto carattere è invece il valore che devo scrivere. Nel nostro caso 0x000000.

L'ultimo parametro specifica se scrivere il valore passando per il bufferpool. Noi mettiamo il valore 1 per scrivere direttamente sul file mdf fisico.

Quindi proviamo? vediamo cosa accade?
Via!

Mettiamo il database nella modalita SINGLE_USERS tramite questo comando:


ALTER DATABASE DB_CORROTTO SET SINGLE_USER WITH ROLLBACK IMMEDIATE

Poi eseguiamo ....sempre con un po di suspance il comando:

DBCC WRITEPAGE ('DB_CORROTTO', 1, 264, 8, 3, 0x000000,1)

Terminata l'esecuzione, controlliamo nuovamente l'integrità del database sempre con il comando DBCC CHECKDB:

Et voilà! Evviva! Zero errori


CHECKDB ha trovato 0 errori di allocazione e 0 errori di coerenza nel database 'DB_Corrotto'.
Esecuzione DBCC completata. Se sono stati visualizzati messaggi di errore DBCC, rivolgersi all'amministratore di sistema.


Mettiamo infine il database in modalità multiutente ed abbiamo terminato.

ALTER DATABASE DB_CORROTTO SET MULTI_USER 


Quindi, è tutto rosa e fiori? 

Bhe non proprio, questo che abbiamo analizzato è un caso molto semplice.Spesso la situazione è decisamente più complicata e richiede quantomeno un analisi molto molto più approfondita!


Per oggi vi saluto, sperando di avervi fatto appassionare o almeno incuriosire un po.
Se avete domande scrivetele! ...mi raccomando!

Ciao ed alla prossima .... STAY TUNED!

Luca Biondi @ SQLServerPerformance blog!









Next post: SQL Server: Come fare ricerche CASE SENSITIVE

Previous post: Database Corrotti e la tabella suspect_pages





Comments

I Post più popolari

SQL Server, execution plan and the lazy spool (clearly explained)

SQL Server, datetime vs. datetime2

La clausola NOLOCK. Approfondiamo e facciamo chiarezza!