Backup Metadata Fun

Until yesterday I never heard of World Backup Day.  I checked Target and I couldn’t find a single greeting card either.  I was wondering if I was being Punk’d by Brent Ozar in his post that tomorrow (March 31, 2012) has that distinction.  Then I figured what the heck, I’ve some scripts I use frequently that involve tapping into backup and restore metadata that would be fun to share with the community.

Coincidentally I’ll be discussing these and other scripts I use to work smarter and not harder in Madison, Wisconsin in April at SQL Saturday 118 in my presentation: Performance-Enhancing Laziness.

Latest Backup Information

I frequently want to determine when my databases were last backed up.  I use this information to satisfy such issues as restore requests, determining backup aging tolerances are being met, and in analyzing retention policies as well as for backup volume capacity planning.  This information is the basis of one of my Morning Coffee Reports that are generated daily from SQL Server Reporting Services that I review while grabbing that first cup of liquid courage to see how my day is going to fare.  I look at both full backups and transaction log backups independently.  I believe Brent presented a similar script but I also incorporate backup location into my results because I not only want to see when the last backup occurred; I also want to know where it is.

Last Full Database Backup for Each Database

WITH full_backups AS
(
SELECT
	ROW_NUMBER() OVER(PARTITION BY database_name ORDER BY database_name ASC, backup_finish_date DESC) AS [Row Number]
	, database_name
	, backup_set_id
	, backup_finish_date
FROM msdb.dbo.[backupset]
WHERE [type] = 'D'
)
 
SELECT
	BS.server_name
	, BS.database_name
	, FB.backup_finish_date
	, BMF.physical_device_name
	, BMF.logical_device_name AS [backup_device_name]
FROM full_backups FB
	INNER JOIN msdb.dbo.[backupset] BS ON FB.backup_set_id = BS.backup_set_id
	INNER JOIN msdb.dbo.backupmediafamily BMF ON BS.media_set_id = BMF.media_set_id
WHERE FB.[Row Number] = 1
ORDER BY FB.database_name;

 

Last Transaction Log Backup for Each Database Running in Full or Bulk-Logged Recovery

WITH log_backups AS
(
SELECT
	ROW_NUMBER() OVER(PARTITION BY database_name ORDER BY database_name ASC, backup_finish_date DESC) AS [Row Number]
	, database_name
	, backup_set_id
	, backup_finish_date
FROM msdb.dbo.[backupset]
WHERE [type] = 'L'
)
 
SELECT
	BS.server_name
	, BS.database_name
	, FB.backup_finish_date
	, BMF.physical_device_name
	, BMF.logical_device_name AS [backup_device_name]
FROM log_backups FB
 INNER JOIN msdb.dbo.[backupset] BS ON FB.backup_set_id = BS.backup_set_id
 INNER JOIN msdb.dbo.backupmediafamily BMF ON BS.media_set_id = BMF.media_set_id
WHERE FB.[Row Number] = 1
ORDER BY FB.database_name;

 

Is Every Database Getting Backed Up?

When I answer this question I run the identical script Brent blogged about in his post.  I encourage you to check it out there.

 

When is this @&*#ing Backup/Restore Going to @&*#ing Finish?

A watched pot never boils and a watched backup or restore never finishes. The nice thing is there is a column in the sys.dm_exec_requests Dynamic Management Object that does provide a percentage of completion metric for certain commands that does allow you to estimate when the process will complete however. Keep in mind that SQL Server has much in common with palm readers, fortune tellers and con artists though when it comes to providing you with an accurate estimate of completion time: the more information your provide (in this case, more bits that are written to disk) the better the estimate of completion.

Since I’m using the value of sys.dm_exec_requests.percent_complete to return results this query will not be limited to just BACKUP and RESTORE commands. That means you’ll also be able to answer the following questions as well with the same query:

When will this @&*#ing DBCC CHECKDB or DBCC CHECKTABLE etc. complete?
When will this @&*#ing SHRINK DATABASE be over?
When will this @&*#ing SHRINKFILE finish?
When will this @&*#ing INDEX DEFRAG or ALTER INDEX REORGANIZE be done?
When will this @&*#ing ROLLBACK TRANSACTION finish?

Please note however it will not answer the question concerning how many cute, fluffy, innocent kittens you’re viciously murdering in cold blood by performing that SHRINK DATABASE command.

SELECT eR.session_id
	, eR.percent_complete
	, eR.total_elapsed_time/1000 AS elapsed_secs
	, DATEADD(s,100/((eR.percent_complete) / (eR.total_elapsed_time/1000)), eR.start_time) AS est_completion_time
	, eST.text
FROM sys.dm_exec_requests eR
	CROSS APPLY sys.dm_exec_sql_text(eR.sql_handle) eST
WHERE eR.percent_complete > 0
	AND eR.session_id <> @@spid;

 

Use Backup History to Examine Database File Growth

I typically advocate only retaining 30 days of msdb job and backup history for your SQL Servers. There is no scheduled or configurable task to keep a running X days of history so you must roll your own solution to avoid not only msdb growing wildly un-checked like bacon references on Twitter but to avoid the pitfalls of deficiencies in indexing of tables within msdb. There is a side benefit of withheld records in msdb though… you can use it to get a rough idea of database file growth trending without persisting information from master.sys.database_files or master.sys.all_files to a user database elsewhere.

SELECT
	BS.database_name
	, CAST(BF.file_size/1024/1024 AS bigint) AS file_size_mb
	, CAST(BF.backup_size/1024/1024 AS bigint) AS consumed_size_mb
	, BF.logical_name
	, BF.physical_name
	, BS.backup_finish_date AS polling_date
FROM msdb.dbo.backupset BS
	INNER JOIN msdb.dbo.backupfile BF ON BS.backup_set_id = BF.backup_set_id
WHERE  BS.type = 'D'
ORDER BY BS.database_name
	, BF.logical_name
	, BS.backup_finish_date DESC;

 

Determine Compression Factor for Individual Databases

If you’re using a recent version of SQL Server that supports compression then the following query will allow you to estimate your space savings by going the route of compressing backups. Different types of data compress differently. This query will toss errors if you run it on a version of SQL Server that does not support compressed backup funtionality (SQL Server 2005 and older.)

WITH full_backups AS
(
SELECT
	ROW_NUMBER() OVER(PARTITION BY BS.database_name ORDER BY BS.database_name ASC, BS.backup_finish_date DESC) AS [Row Number]
	, BS.database_name
	, BS.backup_set_id
	, BS.backup_size AS uncompressed_size
	, BS.compressed_backup_size AS compressed_size
	, CAST((BS.compressed_backup_size / BS.backup_size) AS decimal(4,1)) AS compression_ratio
	, BS.backup_finish_date
FROM msdb.dbo.[backupset] BS
WHERE BS.[type] = 'D'
)
 
SELECT
	FB.database_name
	, FB.backup_finish_date
	, FB.uncompressed_size
	, FB.compressed_size
	, FB.compression_ratio
FROM full_backups FB
 INNER JOIN msdb.dbo.[backupset] BS ON FB.backup_set_id = BS.backup_set_id
 INNER JOIN msdb.dbo.backupmediafamily BMF ON BS.media_set_id = BMF.media_set_id
WHERE FB.[Row Number] = 1
ORDER BY FB.database_name;

 

Happy World Backup Day

Now that you’ve had a chance to see what more there is to do with the data contained in msdb on your SQL Servers get out there and celebrate World Backup Day as tradition holds. Don your kilt, pour a glass of bacon gravy, sing the songs we all learned as children and backup up all that personal data we now keep out in the cloud.