Auto generate database copy with backup and restore

Vedo spesso la necessità di poter disporre di un database “copia”, per attività di controllo, verifica, reportistica, …

In pratica, risolvere la necessità di costruire al volo e dinamicamente una copia del database, “accanto” a quello operazionale.

Per rendere l’idea:

image

 

Possiamo automatizzare tutto tramite script (più in basso in questo post)

Tre sono le variabili che devono essere impostate per il corretto funzionamento:

image

  1. definire il nome del database che vogliamo copiare
  2. definire il path dove memorizzare il backup del database da copiare
  3. definire il path dove effettuare il restore

 

Le istruzioni seguenti, elencando le attività in una sorta di workflow, effettuano:

  • impostazione delle variabili necessarie a definire db, path, nomi file, ecc…
  • full database backup (compresso e in copy_only)
  • recupero nomi logici e fisici dei file del db (necessari ad effettuare la restore in maniera dinamica)
  • restore database

 

Questo lo script (che può essere inserito, per semplicità d’uso, in un’opportuna procedura):

 USE MASTER
GO

SET NOCOUNT ON;
SET XACT_ABORT ON;

/* Procedure Variables */
DECLARE @backupFile nvarchar( 100 );
DECLARE @databaseName nvarchar( 100 );
DECLARE @tSQL nvarchar( 2000 );
DECLARE @NEWdatabaseName nvarchar( 100 );
DECLARE @pathRestore nvarchar(1000);
DECLARE @restoreCommand nvarchar( 2000 );



/* *** SET VARIABLES *** */
SET @databaseName        = 'AdventureWorks2012'; 

SET @backupFile        = 'c:\data\';
SET @pathRestore        = 'c:\data\';
/* *** SET VARIABLES *** */


/* build the new database name. es: myDB_20121220_122543 */
SET @NEWdatabaseName    = 
    (select @databaseName + '_' + 
       convert(char(8), getdate(),112) + '_' + replace(convert(char(8), getdate(),108),':',''))

SET @backupFile = @backupFile + @NEWdatabaseName + '.bck'

print '@NEWdatabaseName: ' + @NEWdatabaseName
print '@backupFile: ' + @backupFile
print ''


/* FULL DATABASE BACKUP - compression, copy_only*/
/* compose the BACKUP statement */
set @tSQL = 
    'BACKUP DATABASE ' + @databaseName + ' TO DISK= ''' + @backupFile + '''
    WITH 
       COMPRESSION,    /* Explicitly enables backup compression */
       FORMAT,        /* FORMAT causes the backup operation to write a new 
                       media header on all media volumes used for the backup operation */
       COPY_ONLY        /* A copy-only backup is created independently 
                       of your regularly scheduled, conventional backups */
    '
/* execute BACKUP */
PRINT ''
PRINT @tsql;
EXECUTE sp_executesql @tsql;

SET @tSQL = 
'
IF EXISTS( SELECT 1
             FROM sys.databases
             WHERE name = ''' + @NEWdatabaseName  + ''' )
    BEGIN
       ALTER DATABASE ' + @NEWdatabaseName + '
       SET SINGLE_USER WITH
        ROLLBACK IMMEDIATE
    END
'
PRINT @tsql;
EXECUTE sp_executesql @tsql;
PRINT ''


/* RETRIEVE LOGICAL AND PHYSICAL INFO FROM BACKUP TO EXECUTE RESTORE DINAMICALLY */
    DECLARE @fileList TABLE
    (
       LogicalName  nvarchar(128),
       PhysicalName nvarchar(260),
       Type   char(1),
       FileGroupName nvarchar(128) null,
       Size   numeric(20, 0),
       MaxSize   numeric(20, 0),
       FileId   int null,
       -- Flags   int null,
       CreateLSN    numeric(25,0) null,
       DropLSN     numeric(25,0) null,
       UniqueFileId  uniqueidentifier null,
       readonlyLSN     numeric(25,0) null,
       readwriteLSN     numeric(25,0) null,
       BackupSizeInBytes bigint null,
       SourceBlkSize  int null,
       FileGroupId  int null,
       LogGroupGuid  uniqueidentifier null,
       DifferentialBaseLsn numeric(25,0) null,
       DifferentialBaseGuid uniqueidentifier null,
       IsReadOnly  bit null,
       IsPresent  bit null,
       TDEThumbprint varbinary(32)
    )

    set @tSQL = 'RESTORE FILELISTONLY FROM DISK = ''' + @backupFile + ''''
    PRINT ''
    PRINT @tsql;
    INSERT @fileList
    EXECUTE sp_executesql @tsql;

-- Build the T-SQL RESTORE statement 
SET @restoreCommand = 'RESTORE DATABASE ' + @NEWdatabaseName + ' FROM DISK = ''' + @backupFile + ''' WITH REPLACE, '; 
    SELECT @restoreCommand = @restoreCommand + CHAR( 13 ) + ' MOVE ''' + LogicalName + ''' TO ''' + 
          @pathRestore + LogicalName + '_NEW' + '.' + 
          RIGHT( PhysicalName , CHARINDEX( '\' , PhysicalName )) + ''','
     FROM @fileList
     WHERE IsPresent = 1;
    SET @restoreCommand = SUBSTRING( @restoreCommand , 1 , LEN( @restoreCommand ) - 1 ); 
    PRINT ''
    PRINT @restoreCommand;

EXEC sp_executesql @restoreCommand;


set @tSQL = 
'
ALTER DATABASE ' + @NEWdatabaseName + '
SET MULTI_USER
'
PRINT @tsql;
EXECUTE sp_executesql @tsql;
GO

 

E se volessimo effettuare la copia su un’istanza differente?

Le istruzioni si complicano di poco, dovendo:

  • costruire una procedura, sull’istanza remota, che contenga le istruzioni per effettuare il restore in locale (attenzione: non è possibile chiamare, dal server locale, una RESTORE sul server remoto. Serve necessariamente una procedura già presente sulla seconda istanza)
  • costruire, in locale, un linked server verso l’istanza remota, ad esempio:
 EXEC sp_addlinkedserver   
   @server=N'SQLRestoreInstance', 
   @srvproduct=N'',
   @provider=N'SQLNCLI', 
   @datasrc=N'(local)\SQL2012_2';

EXEC master.dbo.sp_serveroption 
    @server=N'SQLRestoreInstance', 
    @optname=N'rpc out', 
    @optvalue=N'true'
GO

EXEC master.dbo.sp_addlinkedsrvlogin 
    @rmtsrvname = N'SQLRestoreInstance', 
    @locallogin = N'<local login>', 
    @useself = N'False', 
    @rmtuser = N'<remote user>', 
    @rmtpassword = N'<the password of user on remote instance>'
GO

Una volta creato il linked server, il nostro codice locale, una volta effettuato il backup, dovrà chiamare la procedura remota.

Ad esempio:

 EXEC SQLRestoreInstance.Master.dbo.sp_restoreBackupDB

 

Enjoy.