Skip to content

Instantly share code, notes, and snippets.

@rsyuzyov
Last active July 20, 2022 11:43
Show Gist options
  • Save rsyuzyov/1e412fa650b5c64ce6d5abd018da8254 to your computer and use it in GitHub Desktop.
Save rsyuzyov/1e412fa650b5c64ce6d5abd018da8254 to your computer and use it in GitHub Desktop.
Восстановление БД из бэкапа, созданного с помощью решения Ola Hallengren
/*
Скрипт для восстановления базы из бэкапов, сделанных с помощью решения Ola Hallengren (https://ola.hallengren.com)
Сделано по мотивам http://jason-carter.net/professional/restore-script-from-backup-directory-modified.html
и http://www.codepimp.org/2015/04/automated-restore-using-ola-hallengrens-sql-server-backup/
На github есть готовые решения для восстановления из ola-бэкапов, это просто еще один вариант со своими особенностями:
- восстановление баз на любом (не исходном) сервере
- восстановление в указанный каталог
- восстановление на момент времени
- восстановление не только от последнего полного бэкапа, то есть можно указать любую дату в прошлом, были бы бэкапы
- скрипт умеет обрывать соединения с конечной базой перед восстановлением
- скрипт сделан своими руками для тренировки :)
Для работы требуется разрешить xp_cmdshell:
EXEC sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO
EXEC sp_configure 'xp_cmdshell', 1;
GO
RECONFIGURE;
GO
*/
SET NOCOUNT ON;
---Set stored procedure variables
DECLARE @SourceDBName sysname = 'БизнесШкола' -- Имя исходной базы
DECLARE @DestinationDBName sysname -- Имя конечной базы. Если не задано, то будет @SourceDBName + '_'. Пример: 'НоваяБаза'
DECLARE @RootPath NVARCHAR(500) = '\\srv-vm3\SQL Backup' -- Каталог с бэкапами. Можно указывать общий путь (как в скриптах ola), либо полный путь для базы. Примеры: '\\КаталогБэкапов' или '\\КаталогБэкапов\ИмяСервера\ИмяБазы'
DECLARE @ServerName NVARCHAR(100) -- Если @RootPath указан без имени сервера и базы, то имя сервера используется для вычисления полного пути к бэкапам. Если не указан, то берется имя текущего сервера
DECLARE @RestoreDateTime NVARCHAR(500) -- Момент времени для восстановления. Примеры: '20150402001601'
DECLARE @DestinationFolder NVARCHAR(500) -- Каталог для размещения файлов БД. Если не задан, то будет использован исходное расположение. Пример: 'C:\Temp'
DECLARE @DropConnections int = 1 -- закрывать соединения
DECLARE @DebugLevel int = 0 -- null или 0 - выполнение восстановления, 1 - вывод текста срипта для восстановления без выполнения, 2 - вывод значений переменных и текста скрипта без выполнения
--Variable declaration.
DECLARE @backupPath NVARCHAR(500)
DECLARE @cmd NVARCHAR(500)
DECLARE @fileList TABLE (backupFile NVARCHAR(255))
DECLARE @lastFullBackup NVARCHAR(500)
DECLARE @lastDiffBackup NVARCHAR(500)
DECLARE @backupFile NVARCHAR(500)
DECLARE @SQL VARCHAR(MAX)
DECLARE @backupDBName NVARCHAR(255)
--Define version number
DECLARE @ProductVersion NVARCHAR(128) = CONVERT(NVARCHAR(128),SERVERPROPERTY('ProductVersion'))
DECLARE @ProductVersionNumber TINYINT = SUBSTRING(@ProductVersion, 1, (CHARINDEX('.', @ProductVersion) - 1))
--Preparation
SET @backupDBName = REPLACE(@SourceDBName, ' ', '')
IF @DestinationDBName IS NULL SET @DestinationDBName = @SourceDBName + '_'
IF @DebugLevel IS NULL SET @DebugLevel = 0
IF @ServerName IS NULL SET @ServerName = @@SERVERNAME
IF (SELECT CHARINDEX(@SourceDBName, @RootPath)) = 0 SET @RootPath = @RootPath + '\' + @ServerName + '\' + @backupDBName
IF DB_ID(@DestinationDBName) IS NOT NULL AND @DropConnections = 1
BEGIN
SET @cmd = 'ALTER DATABASE [' + @DestinationDBName + ']'
+ CHAR(13) + 'SET SINGLE_USER WITH ROLLBACK IMMEDIATE'
+ CHAR(13) + 'DROP DATABASE [' + @DestinationDBName + ']'
+ CHAR(13)
IF @DebugLevel = 0 EXEC sp_executesql @cmd
ELSE PRINT @cmd
END
--========== FULL BACKUP RESTORE ==========--
SET @backupPath = @RootPath + '\FULL\'
IF @DebugLevel > 1 PRINT '-- Restore full backup from ' + @backupPath;
--Get the list of backups files
SET @cmd = 'DIR /b ' + '"' + @backupPath + '"'
INSERT INTO @fileList (backupFile) EXEC master.sys.xp_cmdshell @cmd
--Find latest full backup
IF @RestoreDateTime IS NULL
SELECT @lastFullBackup = MAX(backupFile) FROM @fileList
WHERE backupFile LIKE '%_FULL_%' AND backupFile LIKE '%' + @backupDBName + '%'
ELSE
BEGIN
DECLARE @currentLogBackup VARCHAR(255), @previousLogBackup VARCHAR(255), @startDateTime VARCHAR(255), @DateTimeValue VARCHAR(255);
DECLARE backupFiles CURSOR FOR
SELECT backupFile FROM @fileList
WHERE backupFile LIKE '%_FULL_%' AND backupFile LIKE '%' + @backupDBName + '%'
ORDER BY backupFile
OPEN backupFiles
FETCH NEXT FROM backupFiles INTO @backupFile
WHILE @@FETCH_STATUS = 0
BEGIN
IF @RestoreDateTime > REPLACE(LEFT(RIGHT(@backupFile, 19), 15), '_', '') SET @lastFullBackup = @backupFile
ELSE BREAK
FETCH NEXT FROM backupFiles INTO @backupFile
END
CLOSE backupFiles
DEALLOCATE backupFiles
END
IF @lastFullBackup IS NULL BEGIN PRINT '--No backups found before ' + @RestoreDateTime + ', aborted' RETURN END
--Generate cmd
SET @cmd = 'RESTORE DATABASE [' + @DestinationDBName + '] FROM DISK = ''' + @backupPath + @lastFullBackup + ''' WITH REPLACE, NORECOVERY'
IF @SourceDBName != @DestinationDBName OR @DestinationFolder IS NOT NULL
BEGIN
IF OBJECT_ID('tempdb..#FileList') IS NOT NULL DROP TABLE #FileList
CREATE TABLE #FileList (LogicalName VARCHAR(128), [PhysicalName] VARCHAR(128), [Type] VARCHAR, [FileGroupName] VARCHAR(128), [Size] VARCHAR(128),
[MaxSize] VARCHAR(128), [FileId]VARCHAR(128), [CreateLSN]VARCHAR(128), [DropLSN]VARCHAR(128), [UniqueId]VARCHAR(128), [ReadOnlyLSN]VARCHAR(128), [ReadWriteLSN]VARCHAR(128),
[BackupSizeInBytes]VARCHAR(128), [SourceBlockSize]VARCHAR(128), [FileGroupId]VARCHAR(128), [LogGroupGUID]VARCHAR(128), [DifferentialBaseLSN]VARCHAR(128),
[DifferentialBaseGUID]VARCHAR(128), [IsReadOnly]VARCHAR(128), [IsPresent]VARCHAR(128), [TDEThumbprint]VARCHAR(128)
)
IF @ProductVersionNumber in (13) ALTER TABLE #FileList ADD [SnapshotUrl]NVARCHAR(360) -- sql version dependency
DECLARE @Path VARCHAR(1000) = '' + @backupPath + @lastFullBackup + ''
DECLARE @LogicalNameData VARCHAR(128), @LogicalNameLog VARCHAR(128), @StorageFolder VARCHAR(128)
INSERT INTO #FileList EXEC('RESTORE FILELISTONLY FROM DISK=''' + @Path + '''')
SET @LogicalNameData=(SELECT LogicalName FROM #FileList WHERE Type='D')
SET @LogicalNameLog=(SELECT LogicalName FROM #FileList WHERE Type='L')
IF @DestinationFolder IS NULL
BEGIN
SET @DestinationFolder = (SELECT PhysicalName FROM #FileList where Type='D')
SET @DestinationFolder = SUBSTRING(@DestinationFolder, 0, LEN(@DestinationFolder) - LEN(REVERSE(SUBSTRING(REVERSE(@DestinationFolder), 0, CHARINDEX('\', REVERSE(@DestinationFolder))))))
END
SET @cmd = @cmd + ', '
+ 'MOVE ''' + @LogicalNameData + ''' TO ''' + @DestinationFolder + '\' + @DestinationDBName + '.mdf'', '
+ 'MOVE ''' + @LogicalNameLog + ''' TO ''' + @DestinationFolder + '\' + @DestinationDBName + '.ldf''; '
IF OBJECT_ID('tempdb..#FileList') IS NOT NULL DROP TABLE #FileList
END
--Execute the full restore command
IF @DebugLevel = 0 EXEC sp_executesql @cmd
ELSE PRINT @cmd + CHAR(13)
--========== DIFF BACKUP RESTORE ==========--
SET @backupPath = @RootPath + '\DIFF\'
IF @DebugLevel > 1 PRINT '-- Restore diff backup from ' + @backupPath
--Find the latest differential backup
SET @cmd = 'DIR /b ' + '"' + @backupPath + '"'
INSERT INTO @fileList (backupFile) EXEC master.sys.xp_cmdshell @cmd
SELECT @lastDiffBackup = MAX(backupFile) FROM @fileList WHERE backupFile LIKE '%_DIFF_%' AND backupFile LIKE '%' + @backupDBName + '%'
--Check to make sure there is a diff backup
IF @lastDiffBackup IS NOT NULL
BEGIN
SET @cmd = 'RESTORE DATABASE [' + @DestinationDBName + '] FROM DISK = ''' + @backupPath + @lastDiffBackup + ''' WITH REPLACE, NORECOVERY'
IF @DebugLevel > 1 PRINT '-- @lastFullBackup = ' + @lastFullBackup
IF @DebugLevel = 0 EXEC sp_executesql @cmd
ELSE PRINT @cmd + CHAR(13)
END
--========== LOG RESTORE ==========--
SET @backupPath = @RootPath + '\LOG\'
IF @DebugLevel > 1 PRINT CHAR(13) + '-- Restore log from ' + @backupPath
IF @lastDiffBackup IS NULL SELECT @startDateTime = REPLACE(LEFT(RIGHT(@lastFullBackup, 19), 15), '_', '')
ELSE SELECT @startDateTime = REPLACE(LEFT(RIGHT(@lastDiffBackup, 19), 15), '_', '')
--Get the list of log files that are relevant to the backups being used
SET @cmd = 'DIR /b ' + '"' + @backupPath + '"'
INSERT INTO @fileList (backupFile) EXEC master.sys.xp_cmdshell @cmd
DECLARE backupFiles CURSOR FOR
SELECT backupFile FROM @fileList
WHERE backupFile LIKE '%_LOG_%' AND backupFile LIKE '%' + @backupDBName + '%' AND REPLACE(LEFT(RIGHT(backupFile, 19), 15), '_', '') > @startDateTime
ORDER BY backupFile
OPEN backupFiles
-- Loop through all the files for the database
FETCH NEXT FROM backupFiles INTO @backupFile
SET @previousLogBackup = REPLACE(LEFT(RIGHT(@backupFile, 19), 15), '_', '')
SET @lastFullBackup = REPLACE(LEFT(RIGHT(@lastFullBackup, 19),15),'_','')
IF @RestoreDateTime < @startDateTime BEGIN PRINT '-- Invalid @RestoreDateTime - it must be a value greater than the last full or diff backup' RETURN END
WHILE @@FETCH_STATUS = 0
BEGIN
SET @currentLogBackup = REPLACE(LEFT(RIGHT(@backupFile, 19), 15), '_', '')
IF @RestoreDateTime IS NULL
BEGIN
IF @currentLogBackup > @startDateTime
BEGIN
SET @cmd = 'RESTORE LOG [' + @DestinationDBName + '] FROM DISK = ''' + @backupPath + @backupFile + ''' WITH REPLACE, NORECOVERY'
IF @DebugLevel = 0 EXEC sp_executesql @cmd
ELSE PRINT @cmd
END
END
ELSE
IF @currentLogBackup < @RestoreDateTime
BEGIN
SET @cmd = 'RESTORE LOG [' + @DestinationDBName + '] FROM DISK = ''' + @backupPath + @backupFile + ''' WITH NORECOVERY'
IF @DebugLevel = 0 EXEC sp_executesql @cmd
ELSE PRINT @cmd
END
ELSE
IF (@RestoreDateTime > @previousLogBackup AND @RestoreDateTime < @currentLogBackup) OR @RestoreDateTime < @previousLogBackup
BEGIN
SET @DateTimeValue = CONVERT(VARCHAR, CONVERT(DATETIME, SUBSTRING(@RestoreDateTime, 1,8)), 104) + ' ' + SUBSTRING(@RestoreDateTime,9,2) + ':' + SUBSTRING(@RestoreDateTime,11,2) + ':' + SUBSTRING(@RestoreDateTime,13,2)
SET @cmd = 'RESTORE LOG [' + @DestinationDBName + '] FROM DISK = ''' + @backupPath + @backupFile + ''' WITH NORECOVERY, STOPAT = ''' + @DateTimeValue + ''''
IF @DebugLevel = 0 EXEC sp_executesql @cmd
ELSE PRINT @cmd
BREAK
END
SET @previousLogBackup = @currentLogBackup
FETCH NEXT FROM backupFiles INTO @backupFile
END
CLOSE backupFiles
DEALLOCATE backupFiles
--End with recovery so that the database is put back into a working state.
SET @cmd = CHAR(13) + 'RESTORE DATABASE [' + @DestinationDBName + '] WITH RECOVERY'
IF @DebugLevel = 0 EXEC sp_executesql @cmd
ELSE PRINT @cmd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment