-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackup_health.sql
More file actions
74 lines (72 loc) · 3.79 KB
/
Copy pathbackup_health.sql
File metadata and controls
74 lines (72 loc) · 3.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/*******************************************************************************
Script Name: backup_health.sql
Purpose: Reviews the latest full, differential and log backups for online user databases on the SQL Server instance.
Scope: SQL Server instance; reads msdb backup history
SQL Server: 2016+
Azure SQL: Azure SQL support varies for msdb and file operations; see docs/COMPATIBILITY.md
Permissions: VIEW DATABASE STATE or read access to msdb backup history, depending on the script
Risk: Read-only; review and test any generated SQL before execution.
Output: Priority, Category, Object, Finding, Evidence, Recommendation, SuggestedSql, Risk
Author: TheMax-Lab
Version: 1.0
License: MIT
*******************************************************************************/
DECLARE @FullBackupWarningHours int = 36;
DECLARE @LogBackupWarningMinutes int = 30;
;WITH b AS
(
SELECT
bs.database_name,
MAX(CASE WHEN bs.type = 'D' THEN bs.backup_finish_date END) AS last_full_backup,
MAX(CASE WHEN bs.type = 'I' THEN bs.backup_finish_date END) AS last_diff_backup,
MAX(CASE WHEN bs.type = 'L' THEN bs.backup_finish_date END) AS last_log_backup
FROM msdb.dbo.backupset AS bs
WHERE bs.is_copy_only = 0
GROUP BY bs.database_name
)
SELECT
CASE
WHEN b.last_full_backup IS NULL THEN 'High'
WHEN DATEDIFF(hour, b.last_full_backup, GETDATE()) >= @FullBackupWarningHours THEN 'High'
WHEN d.recovery_model_desc IN (N'FULL', N'BULK_LOGGED')
AND (b.last_log_backup IS NULL
OR DATEDIFF(minute, b.last_log_backup, GETDATE()) >= @LogBackupWarningMinutes)
THEN 'High'
ELSE 'Low'
END AS [Priority],
'Backup' AS [Category],
QUOTENAME(d.name) AS [Object],
CASE
WHEN b.last_full_backup IS NULL THEN 'No non-copy-only full backup found in msdb history'
WHEN DATEDIFF(hour, b.last_full_backup, GETDATE()) >= @FullBackupWarningHours THEN 'Full backup is older than the review threshold'
WHEN d.recovery_model_desc IN (N'FULL', N'BULK_LOGGED') AND b.last_log_backup IS NULL THEN 'No log backup found in msdb history'
WHEN d.recovery_model_desc IN (N'FULL', N'BULK_LOGGED')
AND DATEDIFF(minute, b.last_log_backup, GETDATE()) >= @LogBackupWarningMinutes
THEN 'Log backup is older than the review threshold'
ELSE 'Recent backup history found'
END AS [Finding],
CONCAT(
'recovery=', d.recovery_model_desc,
'; last full=', COALESCE(CONVERT(varchar(19), b.last_full_backup, 120), 'never/unknown'),
'; last diff=', COALESCE(CONVERT(varchar(19), b.last_diff_backup, 120), 'never/unknown'),
'; last log=', COALESCE(CONVERT(varchar(19), b.last_log_backup, 120), 'never/unknown')
) AS [Evidence],
'Validate the backup chain, backup destination, restore testing, retention policy, RPO/RTO and whether a third-party product records history in msdb. A successful backup is not proof that restore will succeed.' AS [Recommendation],
'-- Review backup jobs and perform scheduled restore tests. Do not change recovery model merely to suppress a backup warning.' AS [SuggestedSql],
'Low: read-only. Operational risk is High if backup history is assumed to prove recoverability without restore testing.' AS [Risk]
FROM sys.databases AS d
LEFT JOIN b
ON b.database_name = d.name
WHERE d.database_id > 4
AND d.state_desc = N'ONLINE'
ORDER BY
CASE
WHEN b.last_full_backup IS NULL THEN 0
WHEN DATEDIFF(hour, b.last_full_backup, GETDATE()) >= @FullBackupWarningHours THEN 0
WHEN d.recovery_model_desc IN (N'FULL', N'BULK_LOGGED')
AND (b.last_log_backup IS NULL
OR DATEDIFF(minute, b.last_log_backup, GETDATE()) >= @LogBackupWarningMinutes)
THEN 0
ELSE 1
END,
d.name;