Verificare, in maniera certa, un backup: faccio il restore!

Come fa anche l'amico Luca, mi sforzo spesso di ripetere che l'unico modo sicuro di essere certi di un backup è quello di farne un restore.

Purtroppo sembra che questa legge non sia così conosciuta...

La cosa interessante è che possiamo automatizzare tutto il processo di verifica con uno sforzo pari a zero!

Ovvero costruire una procedura che prenda in ingresso:

  1. il backup da verificare
  2. un path su cui "appoggiare" i file del database di cui faremo il restore

Chiaramente effettueremo un restore su un db differente da quello di produzione, penso fosse chiaro ma preferisco esplicitarlo :-)

Per risolvere il nostro problema abbiamo una precisa strada da percorrere:

  1. tenere traccia della versione di SQL Server in uso (il comando RESTORE FILELISTONLY produce risultati differenti)
  2. recuperare, tramite la RESTORE FILELISTONLY, i nomi logici e fisici dei file che compongono il database backuppato
  3. costruire un'opportuna istruzione di RESTORE con
    • un nome di database che non esista (io uso un guid per sicurezza)
    • clausole MOVE per creare nuovi file fisici
  4. eseguire l'istruzione di RESTORE
  5. eseguire l'istruzione di DROP DATABASE per ripulire le attività

Ovvero, traducendo in TSQL, qualcosa come:

 

if object_id('dbo.up_verifyBackup') IS NOT NULL drop procedure dbo.up_verifyBackup GO create procedure dbo.up_verifyBackup ( @pathBackupDb nvarchar(200), @pathTestRestore nvarchar(200) ) as set nocount on /* Parametri in ingresso: @pathTestRestore: il path dove voglio andare a scrivere i file che compongono il db da restorare @pathBackupDb: il path completo del backup di cui voglio fare test di restore Esempio di esecuzione: exec dbo.up_verifyBackup @pathBackupDb = 'C:\myBackupFolder\myBackup.bak', @pathTestRestore = 'C:\myFolder' */ /* dichiarazione variabili d'appoggio */ declare @tsql nvarchar(4000) declare @databaseName varchar(36) declare @posizioneVirgola int print convert(varchar(10), GETDATE(), 103) + ' ' + convert(varchar(8), GETDATE(), 108) + ' Impostazione variabili' /* tengo traccia della versione di SQL Server 9 = sql server 2005 10 = sql server 2008 questo perchè il risultato ritornato dalla RESTORE FILELISTONLY è differente tra le due versioni */ declare @sqlServerVersion tinyint select @sqlServerVersion = LEFT(CAST( SERVERPROPERTY('productversion') as varchar(15)), CHARINDEX ('.', CAST( SERVERPROPERTY('productversion') as varchar(15))) -1) print '@sqlServerVersion = ' + cast(@sqlServerVersion as varchar(2)) /* comando per recuperare i nomi logici dei file database */ set @tsql = 'RESTORE FILELISTONLY FROM DISK = ''' + @pathBackupDb + '''' print '@pathTestRestore = ' + @pathTestRestore print '@pathBackupDb = ' + @pathBackupDb print '@tsql = ' + @tsql print '' /* creo una tabella temporanea che conterrà il risultato della RESTORE FILELISTONLY devo interrogarla successivamente per recuperare i nomi file */ if object_id('tempdb..#fileListOnly') is not null drop table #fileListOnly create table #fileListOnly ( LogicalName nvarchar(128), PhysicalName nvarchar(260), Type char(1), FileGroupName nvarchar(128), Size numeric(20,0), MaxSize numeric(20,0), FileID bigint, CreateLSN numeric(25,0), DropLSN numeric(25,0) NULL, UniqueID uniqueidentifier, ReadOnlyLSN numeric(25,0) NULL, ReadWriteLSN numeric(25,0) NULL, BackupSizeInBytes bigint, SourceBlockSize int, FileGroupID int, LogGroupGUID uniqueidentifier NULL, DifferentialBaseLSN numeric(25,0) NULL, DifferentialBaseGUID uniqueidentifier, IsReadOnly bit, IsPresent bit ) if (@sqlServerVersion = 10) begin alter table #fileListOnly add TDEThumbprint nvarchar(max) /* nuovo in SQL Server 2008 */ end /* eseguo la RESTORE FILELISTONLY */ print convert(varchar(10), GETDATE(), 103) + ' ' + convert(varchar(8), GETDATE(), 108) + ' Esecuzione @tsql' print '' insert #fileListOnly exec sp_executesql @tsql /* se voglio vedere il risultato della RESTORE FILELISTONLY: select * from #fileListOnly */ /* costruisco il comando di restore */ set @databaseName = (select cast(NEWID() as varchar(36))) print '@databaseName = ' + @databaseName set @tsql = ' RESTORE DATABASE [' + @databaseName + '] FROM DISK = ''' + @pathBackupDb + ''' WITH ' select @tsql = @tsql + ', MOVE N''' + logicalName + ''' TO N''' + @pathTestRestore + '\testRestore_' + RIGHT ( PhysicalName, CHARINDEX ( '\', REVERSE( PhysicalName ) ) - 1) + ''' ' from #fileListOnly /* elimino la prima virgola */ set @posizioneVirgola = (select CHARINDEX(',', @tsql)) set @tsql = STUFF(@tsql, @posizioneVirgola, 1,'') /* vedo il comando di restore */ print '@tsql = ' + @tsql print '' /* se voglio mandare in esecuzione il comando di restore: */ print convert(varchar(10), GETDATE(), 103) + ' ' + convert(varchar(8), GETDATE(), 108) + ' Esecuzione RESTORE' print '' exec sp_executesql @tsql /* se tutto va a buon fine significa che il backup è valido posso droppare il db appena creato: */ print convert(varchar(10), GETDATE(), 103) + ' ' + convert(varchar(8), GETDATE(), 108) + ' Esecuzione DROP DATABASE' print '' set @tsql = 'drop database [' + @databaseName + ']' exec sp_executesql @tsql print '@tsql = ' + @tsql print convert(varchar(10), GETDATE(), 103) + ' ' + convert(varchar(8), GETDATE(), 108) + ' Fine operazioni' go

Per provarla, supponendo di avere un database "Test":

backup database test to disk = 'c:\temp\myBackupTest.bak'

Chiamo la procedura di verifica backup:

exec dbo.up_verifyBackup @pathBackupDb = 'c:\temp\myBackupTest.bak', @pathTestRestore = 'C:\temp' go

Questo il risultato che appare nella mia finestra Messages:

17/07/2008 16:24:35 Impostazione variabili
@sqlServerVersion = 10
@pathTestRestore = C:\temp
@pathBackupDb = c:\temp\myBackupTest.bak
@tsql = RESTORE FILELISTONLY FROM DISK = 'c:\temp\myBackupTest.bak'

17/07/2008 16:24:35 Esecuzione @tsql
@databaseName = FE1AD55D-A54F-429C-88E2-C6DDE2F13A17
@tsql =  RESTORE DATABASE [FE1AD55D-A54F-429C-88E2-C6DDE2F13A17]
  FROM  DISK = 'c:\temp\myBackupTest.bak'
  WITH  MOVE N'test' TO N'C:\temp\testRestore_test.mdf' , MOVE N'test_log' TO N'C:\temp\testRestore_test_log.LDF'

17/07/2008 16:24:35 Esecuzione RESTORE
Processed 176 pages for database 'FE1AD55D-A54F-429C-88E2-C6DDE2F13A17', file 'test' on file 1.
Processed 2 pages for database 'FE1AD55D-A54F-429C-88E2-C6DDE2F13A17', file 'test_log' on file 1.
RESTORE DATABASE successfully processed 178 pages in 0.054 seconds (25.625 MB/sec).

17/07/2008 16:24:35 Esecuzione DROP DATABASE
@tsql = drop database [FE1AD55D-A54F-429C-88E2-C6DDE2F13A17]

17/07/2008 16:24:36 Fine operazioni

Mi sembra comodo, no?

Published giovedì 17 luglio 2008 16.27 by abenedetti

Comments

# re: Verificare, in maniera certa, un backup: faccio il restore!

lunedì 28 luglio 2008 16.46 by david

In effetti l'articolo che hai scritto è comodo ed utile.

Però, visto che con SQL Server 2005 è stato aggiunta la possibilità di scrivere il checksum delle pagine direttamente nel file di backup, e che l'istruzione RESTORE VERIFYLONLY è in grado di verificare quel checksum, mi chiedo se vale la pena di usare la strategia che hai proposto oppure se non sono già abbastanza sicuro usando l'istruzione RESTORE VERIFYLONLY.

Io sinceramente, da quando sono passato a SQL Server 2005, riscontro un problema fastidioso: quando eseguo l'istruzione RESTORE VERIFYLONLY, ricevo sempre il messaggio "Il set di backup del file 'x' non è valido". Chiaramente se provo a fare un RESTORE usando il set di backup, tutto funziona correttamente.

Il problema è che mi da lo stesso messaggio per qualsiasi database e per qualsiasi tipo di backup (completo, differenziale, log).

A questo punto temo che dovrò "accontentarmi" della tua soluzione :-P

# re: Verificare, in maniera certa, un backup: faccio il restore!

lunedì 28 luglio 2008 17.00 by abenedetti

Ciao Davide.

Si, con SQL 2005 l'opzione VERIFYONLY è stata "sistemata" e diventa molto utile se non dovessi avere tutto lo spazio necessario per eseguire il restore fisico.

In SQL 2000 l'opzione si limitava a fare un controllo di validità "formale" del backup.

Comunque, se ho spazio, preferisco fare il restore reale!