Day 3 of 31 Days of Disaster Recovery: Determining Files to Restore Database
Welcome back for day 3 of my month-long series on Disaster Recovery. For today’s post, I want to address something that, in my experience, has really flummoxed a lot of people who find themselves unprepared to handle a disaster scenario. Determining exactly which files you should restore.If you missed any of the earlier posts in the series, you can check them out here:
- 31 Days of disaster Recovery
- Does DBCC Automatically Use Existing Snapshot?
- Protection From Restoring a Backup of a Contained Database
Test Your Restores
Everyone on your team who might be called upon to perform a restore should be performing regular test runs of restoring the backups. It’s this lack of testing that leads to fumbling around trying to figure out what files to restore. Well, whether you’ve practiced or not, you might like a little help to figure out exactly what files are required to restore the database to its most current point. Thus I give you my RestoreScripter script. The script traverses the backup information in the backup tracking tables in the msdb database.
The script automatically handles many of the possible pitfalls that you might experience and gives you the details you need to quickly script out the restores. For example, records with the same “RestoreOrder” are multiple files for the same backup and should be scripted together in a single restore command.
The Script
As usual, rather than explain the various steps in the script here, I have included extensive comments inline in the script at each step. You can grab the script below or download it as a zipped file here: RestoreScripter.zip (2 KB)
Declare @DBName sysname, @DBBackupLSN numeric(25, 0); Declare @Baks Table ( BakID int identity(1, 1) not null primary key, backup_set_id int not null, media_set_id int not null, first_family_number tinyint not null, last_family_number tinyint not null, first_lsn numeric(25, 0) null, last_lsn numeric(25, 0) null, database_backup_lsn numeric(25, 0) null, backup_finish_date datetime null, type char(1) null, family_sequence_number tinyint not null, physical_device_name nvarchar(260) not null, device_type tinyint null) Set NoCount On; -- Set the name of the database you want to restore Set @DBName = N''; -- Get the most recent full backup with all backup files Insert Into @Baks (backup_set_id, media_set_id, first_family_number, last_family_number, first_lsn, last_lsn, database_backup_lsn, backup_finish_date, type, family_sequence_number, physical_device_name, device_type) Select Top(1) With Ties B.backup_set_id, B.media_set_id, B.first_family_number, B.last_family_number, B.first_lsn, B.last_lsn, B.database_backup_lsn, B.backup_finish_date, B.type, BF.family_sequence_number, BF.physical_device_name, BF.device_type From msdb.dbo.backupset As B Inner Join msdb.dbo.backupmediafamily As BF On BF.media_set_id = B.media_set_id And BF.family_sequence_number Between B.first_family_number And B.last_family_number Where B.database_name = @DBName And B.is_copy_only = 0 And B.type = 'D' And BF.physical_device_name Not In ('Nul', 'Nul:') Order By backup_finish_date desc, backup_set_id; -- Get the lsn that the differential backups, if any, will be based on Select @DBBackupLSN = database_backup_lsn From @Baks; -- Get the most recent differential backup based on that full backup Insert Into @Baks (backup_set_id, media_set_id, first_family_number, last_family_number, first_lsn, last_lsn, database_backup_lsn, backup_finish_date, type, family_sequence_number, physical_device_name, device_type) Select Top(1) With Ties B.backup_set_id, B.media_set_id, B.first_family_number, B.last_family_number, B.first_lsn, B.last_lsn, B.database_backup_lsn, B.backup_finish_date, B.type, BF.family_sequence_number, BF.physical_device_name, BF.device_type From msdb.dbo.backupset As B Inner Join msdb.dbo.backupmediafamily As BF On BF.media_set_id = B.media_set_id And BF.family_sequence_number Between B.first_family_number And B.last_family_number Where B.database_name = @DBName And B.is_copy_only = 0 And B.type = 'I' And BF.physical_device_name Not In ('Nul', 'Nul:') And B.database_backup_lsn = @DBBackupLSN Order By backup_finish_date Desc, backup_set_id; -- Get the last LSN included in the differential backup, -- if one was found, or of the full backup Select Top 1 @DBBackupLSN = last_lsn From @Baks Where type In ('D', 'I') Order By BakID Desc; -- Get first log backup, if any, for restore, where -- last_lsn of previous backup is >= first_lsn of the -- log backup and <= the last_lsn of the log backup Insert Into @Baks (backup_set_id, media_set_id, first_family_number, last_family_number, first_lsn, last_lsn, database_backup_lsn, backup_finish_date, type, family_sequence_number, physical_device_name, device_type) Select Top(1) With Ties B.backup_set_id, B.media_set_id, B.first_family_number, B.last_family_number, B.first_lsn, B.last_lsn, B.database_backup_lsn, B.backup_finish_date, B.type, BF.family_sequence_number, BF.physical_device_name, BF.device_type From msdb.dbo.backupset B Inner Join msdb.dbo.backupmediafamily As BF On BF.media_set_id = B.media_set_id And BF.family_sequence_number Between B.first_family_number And B.last_family_number Where B.database_name = @DBName And B.is_copy_only = 0 And B.type = 'L' And BF.physical_device_name Not In ('Nul', 'Nul:') And @DBBackupLSN Between B.first_lsn And B.last_lsn Order By backup_finish_date, backup_set_id; -- Get last_lsn of the first log backup that will be restored Set @DBBackupLSN = Null; Select @DBBackupLSN = Max(last_lsn) From @Baks Where type = 'L'; -- Recursively get all log backups, in order, to be restored -- first_lsn of the log backup = last_lsn of the previous log backup With Logs As (Select B.backup_set_id, B.media_set_id, B.first_family_number, B.last_family_number, B.first_lsn, B.last_lsn, B.database_backup_lsn, B.backup_finish_date, B.type, BF.family_sequence_number, BF.physical_device_name, BF.device_type, 1 As LogLevel From msdb.dbo.backupset B Inner Join msdb.dbo.backupmediafamily As BF On BF.media_set_id = B.media_set_id And BF.family_sequence_number Between B.first_family_number And B.last_family_number Where B.database_name = @DBName And B.is_copy_only = 0 And B.type = 'L' And BF.physical_device_name Not In ('Nul', 'Nul:') And B.first_lsn = @DBBackupLSN Union All Select B.backup_set_id, B.media_set_id, B.first_family_number, B.last_family_number, B.first_lsn, B.last_lsn, B.database_backup_lsn, B.backup_finish_date, B.type, BF.family_sequence_number, BF.physical_device_name, BF.device_type, L.LogLevel + 1 From msdb.dbo.backupset B Inner Join msdb.dbo.backupmediafamily As BF On BF.media_set_id = B.media_set_id And BF.family_sequence_number Between B.first_family_number And B.last_family_number Inner Join Logs L On L.database_backup_lsn = B.database_backup_lsn Where B.database_name = @DBName And B.is_copy_only = 0 And B.type = 'L' And BF.physical_device_name Not In ('Nul', 'Nul:') And B.first_lsn = L.last_lsn) Insert Into @Baks (backup_set_id, media_set_id, first_family_number, last_family_number, first_lsn, last_lsn, database_backup_lsn, backup_finish_date, type, family_sequence_number, physical_device_name, device_type) Select backup_set_id, media_set_id, first_family_number, last_family_number, first_lsn, last_lsn, database_backup_lsn, backup_finish_date, type, family_sequence_number, physical_device_name, device_type From Logs Option(MaxRecursion 0); -- Select out just the columns needed to script restore Select RestoreOrder = Row_Number() Over(Partition By family_sequence_number Order By BakID), RestoreType = Case When type In ('D', 'I') Then 'Database' When type = 'L' Then 'Log' End, DeviceType = Case When device_type in (2, 102) Then 'Disk' When device_type in (5, 105) Then 'Tape' End, PhysicalFileName = physical_device_name From @Baks Order By BakID ; Set NoCount Off;
Sample Output

Sample Restore Files








Twitter
LinkedIn
TLF
RSS
WritersCafe
SQLPASS
Facebook
Hi,
I’d love to hear your opinion on a procedure I’ve just developed that does the same as your script. It includes a STOPAT option which can be used to construct restore scripts based on historic backups/diffs/logs, IE, not necessarily the most recent full.
http://paul.dynalias.com/SQL
Best wishes
Paul
Great script, Paul. One of the things I was trying to demonstrate in this script was basing the association between the different types of backups on the appropriate LSN value. I think that’s safer than basing it only on date.
Your script has shown me the way really, I’ll add a check of the LSN.
Thanks and regards
I gave the script a test run, but every database I ran it against that is in the full recovery model I received the following error: Msg 530, Level 16, State 1, Line 151
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
Thoughts?
That’s a lot of log backups per database, but after thinking about it, if you back up the log every 5 minutes, you could reach that in a little more than 8 hours. I hadn’t thought of that. Thanks for bringing this up. I’ll add the MaxRecursion hint to the recursive CTE.
I think you added the hint to the wrong query.
Thanks Wayne! You are right. I did get it right in the actual script that’s attached, but I seem to have trouble finding the right place inside the HTML of the post.
Change made.
Thats an excellent script Robert,really helpful.
Thanks Anup!
My procedure has been changed to use the backup LSN’s as you do, your join condition to backup media set was also added. You are credited at the bottom , the article and script have been submitted to sql server central. Your series of posts on DR planning are great and very useful.
Thanks
Cool! Thanks for giving me credit! I look forward to seeing it posted.
Pingback: Day 6 of 31 Days of Disaster Recovery: Dealing With Corruption in Allocation Pages | SQLSoldier
Pingback: Day 7 of 31 Days of Disaster Recovery: Writing SLAs for Disaster Recover | SQLSoldier
Pingback: Day 8 of 31 Days of Disaster Recovery (T-SQL Tuesday #38): Resolutions for All DBAs | SQLSoldier
Pingback: Day 9 of 31 Days of Disaster Recovery: Use All the Checksums | SQLSoldier
Pingback: Day 5 of 31 Days of Disaster Recovery: Dealing With Corruption in a Nonclustered Index | SQLSoldier
If you run this against a fresh 2012 install against a system database, is shows a backup for a virtual device, but this is null in the output (the case statement doesn’t consider device_type = 7). And, I don’t think that these can be restored in any case (aren’t they just like backing up to the NUL device?), so would you want to exclude this device type from the queries?
Thanks Wayne. Yes, I think those should be excluded.
Pingback: Day 11 of 31 Days of Disaster: Converting LSN Formats | SQLSoldier
Pingback: Day 12 of 31 Days of Disaster Recovery: Extreme Disaster Recovery Training | SQLSoldier
Pingback: Day 14 of 31 Days of Disaster Recovery: Fixing a Corrupt Tempdb | SQLSoldier
Pingback: Day 15 of 31 Days of Disaster Recovery: Running DBCC CheckTable in Parallel Jobs | SQLSoldier
Fantastic script! Thank you very much for sharing your knowledge. It is greatly appreciated.
Thanks Rick!
Pingback: Day 24 of 31 Days of Disaster Recovery: Handling Corruption in a Clustered Index | SQLSoldier
Quick note: The virtual backup devices are usually third party tools backing up your databases. When things like backup exec backup SQL Server they call VDI that generates a virtual device and assigns it a GUID for the device name.
-Wes
Thanks Wes.