Wprowadzenie
W ciągu ostatnich dwóch lub trzech miesięcy dwukrotnie poproszono mnie o rozwiązanie natywne dla SQL Server, które konsoliduje raport kopii zapasowych dla kilku instancji SQL Server w przedsiębiorstwie. To pytanie zadali znajomi, którzy niekoniecznie chcieli wydawać pieniądze na zakup narzędzia, ale byli bardziej skłonni do korzystania z możliwości SQL Server. Myślałem o dwóch możliwych sposobach osiągnięcia tego celu:
- Korzystanie z połączonych serwerów, widoków katalogu, zadań SQL Agent i poczty bazy danych
- Korzystanie z Centralnego Serwera Zarządzania
W tym artykule zademonstruję pierwszą część i mam nadzieję, że wkrótce będziemy mieli drugą część artykułu.
Scenariusz
Moje środowisko składa się z zestawu trzech instancji umieszczonych na osobnych serwerach na AWS. Te „serwery” to w rzeczywistości Amazon EC2 z systemem SQL Server 2017 RTM CU5. Będziemy również korzystać z usługi Amazon Simple Email Service, aby skonfigurować pocztę bazy danych. W środowisku produkcyjnym z pewnością możesz korzystać z lokalnych serwerów pocztowych i osiągać te same cele. W dalszej części tego artykułu zauważysz, że nazwa hosta (a tym samym nazwy instancji) są takie same. Dzieje się tak, ponieważ serwery zostały sklonowane z tego samego obrazu Amazon Machine (przepraszam za „lenistwo”). To prawdopodobnie nie będzie miało miejsca w przypadku produkcji.
Zrób kilka kopii zapasowych
Zacznijmy od wykonania kilku kopii zapasowych baz danych znajdujących się na tych trzech instancjach. To wygeneruje dane, z którymi będziemy pracować. Następnie sprawdzimy, czy kopie zapasowe są przechwycone w tabelach systemowych msdb.dbo.backupset i msdb.dbo.backupmediafamily . Pełne opisy tych tabel można przejrzeć w tej dokumentacji Microsoft lub po prostu użyć sp_columns .
-- Listing 1: Taking Backups on the Instances -- Backup a single DB with one stripe backup database newdb to disk='newdb.bak' -- Backup all DBs in the instance with timestamp in the backupset name exec sp_MSforeachdb @command1= 'declare @path varchar(300) set @path=''M:\MSSQL\BACKUP\?_Backup'' + convert(varchar(10),getdate(),110) + ''.bak'' print @path backup database [?] to [email protected]' -- Backup a single large DB with four stripes backup database [PieceMealDB] to disk='M:\MSSQL\BACKUP\PieceMealDB_01.bak', disk='M:\MSSQL\BACKUP\PieceMealDB_02.bak', disk='M:\MSSQL\BACKUP\PieceMealDB_03.bak', disk='M:\MSSQL\BACKUP\PieceMealDB_04.bak' with stats=10
Rys 3. Opis msdb.dbo.backupset
Sprawdzanie kopii zapasowych
Poniższy skrypt korzysta z dwóch widoków katalogu zestawu kopii zapasowych i backupmediafamily do zbadania historii kopii zapasowych utworzonych na instancji SQL Server. Katalog zestawów kopii zapasowych zawiera wiersz dla każdego zestawu kopii zapasowych. Zestaw kopii zapasowej jest definiowany jako zawartość operacji tworzenia kopii zapasowej dodawana do zestawu nośników. Zestaw nośników to kolejna kolekcja nośników, na której zapisano jedną lub więcej operacji tworzenia kopii zapasowych.
-- Listing 2: Check Backups using msdb tables -- PRINT 'Checking Databases Successfully Backed Up' use msdb go select bus.database_name,bus.type, case bus.type when 'D' then 'Full' when 'I' then 'Differential' when 'L' then 'Log' end backup_type, bus.backup_start_date, bus.backup_finish_date, (((DATEPART(HH,bus.backup_finish_date))- (DATEPART(HH,bus.backup_start_date)))*3600) + (((DATEPART(MI,bus.backup_finish_date)) - (DATEPART(MI,bus.backup_start_date)))*60) + (((DATEPART(SS,bus.backup_finish_date)) - DATEPART(SS,bus.backup_start_date))) [backup_time (secs)], bus.backup_size, bmf.physical_device_name from backupset bus join backupmediafamily bmf on bus.media_set_id=bmf.media_set_id where bus.backup_start_date >= (getdate() - 7) order by bus.backup_start_date desc
Rys 5. Przykładowe wyjście kontroli kopii zapasowych
Sprawdzanie kopii zapasowych w innych instancjach
Korzystając z Linked Servers, możemy wyodrębnić dane ze zdalnych instancji. W tym przypadku użyjemy prostego serwera połączonego, aby wyodrębnić informacje o historii kopii zapasowych z baz danych msdb dwóch zdalnych instancji. Konfiguracja bezpieczeństwa dla tych serwerów połączonych całkowicie zależy od Ciebie, ale zachowaliśmy bardzo prostą dla celów naszego celu. Listing 3 pokazuje skrypt, który może wykorzystywać te serwery połączone do agregowania danych historii kopii zapasowych.
Rys 6. Prosty serwer połączony
Rys 7. Połączony serwer dla dwóch zdalnych instancji
-- Listing 3: Checking Backups using msdb tables across Linked Servers use msdb go with srva as ( select bus.server_name instance, bus.database_name,bus.type, case bus.type when 'D' then 'Full' when 'I' then 'Differential' when 'L' then 'Log' end backup_type , bus.backup_start_date, bus.backup_finish_date, (((DATEPART(HH,bus.backup_finish_date))- (DATEPART(HH,bus.backup_start_date)))*3600) + (((DATEPART(MI,bus.backup_finish_date)) - (DATEPART(MI,bus.backup_start_date)))*60) + (((DATEPART(SS,bus.backup_finish_date)) - DATEPART(SS,bus.backup_start_date))) [backup_time (secs)], bus.backup_size, bmf.physical_device_name from backupset bus join backupmediafamily bmf on bus.media_set_id=bmf.media_set_id where bus.backup_start_date >= (getdate() - 3) ) , srvb as ( select bus.server_name instance, bus.database_name,bus.type, case bus.type when 'D' then 'Full' when 'I' then 'Differential' when 'L' then 'Log' end backup_type , bus.backup_start_date, bus.backup_finish_date, (((DATEPART(HH,bus.backup_finish_date))- (DATEPART(HH,bus.backup_start_date)))*3600) + (((DATEPART(MI,bus.backup_finish_date)) - (DATEPART(MI,bus.backup_start_date)))*60) + (((DATEPART(SS,bus.backup_finish_date)) - DATEPART(SS,bus.backup_start_date))) [backup_time (secs)], bus.backup_size, bmf.physical_device_name from [10.0.1.155].msdb.dbo.backupset bus join [10.0.1.155].msdb.dbo.backupmediafamily bmf on bus.media_set_id=bmf.media_set_id where bus.backup_start_date >= (getdate() - 3) ) , srvc as ( select bus.server_name instance, bus.database_name,bus.type, case bus.type when 'D' then 'Full' when 'I' then 'Differential' when 'L' then 'Log' end backup_type , bus.backup_start_date, bus.backup_finish_date, (((DATEPART(HH,bus.backup_finish_date))- (DATEPART(HH,bus.backup_start_date)))*3600) + (((DATEPART(MI,bus.backup_finish_date)) - (DATEPART(MI,bus.backup_start_date)))*60) + (((DATEPART(SS,bus.backup_finish_date)) - DATEPART(SS,bus.backup_start_date))) [backup_time (secs)], bus.backup_size, bmf.physical_device_name from [10.0.1.83].msdb.dbo.backupset bus join [10.0.1.83].msdb.dbo.backupmediafamily bmf on bus.media_set_id=bmf.media_set_id where bus.backup_start_date >= (getdate() - 3) ) select * from srva union select * from srvb union select * from srvc;
Włączanie SES i poczty bazy danych
Następnym krokiem, jaki podejmujemy, jest zautomatyzowanie tego sprawdzenia i wysłanie zestawu wyników do administratorów bazy danych. Wymagane kroki są następujące w podsumowaniu:
-
- Skonfiguruj Amazon SES . Możesz dowiedzieć się, jak szybko skonfigurować pocztę e-mail w AWS, korzystając z dokumentacji dostarczonej w Amazon SES Quick Start. W przypadku korzystania z lokalnej usługi poczty e-mail nie będzie to konieczne w przypadku DBA.
- Konfiguruj pocztę bazy danych . Ten artykuł nie jest przeznaczony do demonstracji poczty bazy danych, więc po prostu przedstawiamy zrzut ekranu konfiguracji konta pocztowego SQL:
Rys 7. Ustawienia konta pocztowego SQL
- Numer portu podczas korzystania z SES do wysyłania e-maili to 587, a nie 25
- Amazon SES wymaga bezpiecznego połączenia, dlatego pole wyboru oznaczone kolorem liliowym (ryc. 7) musi być zaznaczone
- Wymagane jest podstawowe uwierzytelnianie przy użyciu poświadczeń SMTP (tj. uwierzytelnianie anonimowe jest niedozwolone).
Podczas korzystania z Amazon SES dla poczty bazy danych musimy tylko pamiętać o kilku rzeczach:
- Skonfiguruj agenta SQL do korzystania z profilu poczty . Agent programu SQL Server musi być skonfigurowany do korzystania z profilu poczty utworzonego podczas konfiguracji poczty bazy danych, aby zadania agenta uruchamiały wiadomości e-mail. (patrz rys. 8)
- Utwórz tabelę pomostową . Tabela pomostowa będzie zawierać zagregowany zestaw wyników dla wszystkich danych historii kopii zapasowych z instancji, na które nakierowaliśmy za pomocą połączonych serwerów. Tabela DDL jest pokazana na Listingu 4.
-- Listing 4: Backup History Table DDL SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[backuphistory]( [instance] [nvarchar](128) NULL, [database_name] [nvarchar](128) NULL, [type] [char](1) NULL, [backup_type] [varchar](12) NULL, [backup_start_date] [datetime] NULL, [backup_finish_date] [datetime] NULL, [backup_time (secs)] [int] NULL, [backup_size] [numeric](20, 0) NULL, [physical_device_name] [nvarchar](260) NULL ) ON [PRIMARY] GO
Rys 8. Ustawienia agenta SQL
Kontynuujemy i planujemy skrypt na liście 3 w zadaniu agenta SQL, a kompletny skrypt mamy na liście 5.
-- Listing 5: Complete SQL Agent Job for Backup History Notification USE [msdb] GO /****** Object: Job [Enteprise Backup History Summary] Script Date: 9/26/2018 10:16:46 PM ******/ BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 /****** Object: JobCategory [[Uncategorized (Local)]] Script Date: 9/26/2018 10:16:46 PM ******/ IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'Enteprise Backup History Summary', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'No description available.', @category_name=N'[Uncategorized (Local)]', @owner_login_name=N'TWENTYTOWERS\Administrator', @job_id = @jobId OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [Aggregate Backup History] Script Date: 9/26/2018 10:16:46 PM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @[email protected], @step_name=N'Aggregate Backup History', @step_id=1, @cmdexec_success_code=0, @on_success_action=3, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N'-- Check Backups using msdb tables -- -- Across Linked Servers use msdb go truncate table [msdb].[dbo].[backuphistory]; with srva as ( select bus.server_name instance, bus.database_name,bus.type, case bus.type when ''D'' then ''Full'' when ''I'' then ''Differential'' when ''L'' then ''Log'' end backup_type , bus.backup_start_date, bus.backup_finish_date, (((DATEPART(HH,bus.backup_finish_date))- (DATEPART(HH,bus.backup_start_date)))*3600) + (((DATEPART(MI,bus.backup_finish_date)) - (DATEPART(MI,bus.backup_start_date)))*60) + (((DATEPART(SS,bus.backup_finish_date)) - DATEPART(SS,bus.backup_start_date))) [backup_time (secs)], bus.backup_size, bmf.physical_device_name from backupset bus join backupmediafamily bmf on bus.media_set_id=bmf.media_set_id where bus.backup_start_date >= (getdate() - 3) ) , srvb as ( select bus.server_name instance, bus.database_name,bus.type, case bus.type when ''D'' then ''Full'' when ''I'' then ''Differential'' when ''L'' then ''Log'' end backup_type , bus.backup_start_date, bus.backup_finish_date, (((DATEPART(HH,bus.backup_finish_date))- (DATEPART(HH,bus.backup_start_date)))*3600) + (((DATEPART(MI,bus.backup_finish_date)) - (DATEPART(MI,bus.backup_start_date)))*60) + (((DATEPART(SS,bus.backup_finish_date)) - DATEPART(SS,bus.backup_start_date))) [backup_time (secs)], bus.backup_size, bmf.physical_device_name from [10.0.1.155].msdb.dbo.backupset bus join [10.0.1.155].msdb.dbo.backupmediafamily bmf on bus.media_set_id=bmf.media_set_id where bus.backup_start_date >= (getdate() - 3) ) , srvc as ( select bus.server_name instance, bus.database_name,bus.type, case bus.type when ''D'' then ''Full'' when ''I'' then ''Differential'' when ''L'' then ''Log'' end backup_type , bus.backup_start_date, bus.backup_finish_date, (((DATEPART(HH,bus.backup_finish_date))- (DATEPART(HH,bus.backup_start_date)))*3600) + (((DATEPART(MI,bus.backup_finish_date)) - (DATEPART(MI,bus.backup_start_date)))*60) + (((DATEPART(SS,bus.backup_finish_date)) - DATEPART(SS,bus.backup_start_date))) [backup_time (secs)], bus.backup_size, bmf.physical_device_name from [10.0.1.83].msdb.dbo.backupset bus join [10.0.1.83].msdb.dbo.backupmediafamily bmf on bus.media_set_id=bmf.media_set_id where bus.backup_start_date >= (getdate() - 3) ) insert into [msdb].[dbo].[backuphistory] select * from srva union select * from srvb union select * from srvc; ', @database_name=N'msdb', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [Query Member Servers for Backups] Script Date: 9/26/2018 10:16:46 PM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @[email protected], @step_name=N'Query Member Servers for Backups', @step_id=2, @cmdexec_success_code=0, @on_success_action=3, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N'DECLARE @tableHTML NVARCHAR(MAX) ; SET @tableHTML = N''<H1><font face="Verdana" size="4">Enterprise Backup History Summary</H1>'' + N''<table border="1"><font face="Verdana" size="2">'' + N''<tr><th><font face="Verdana" size="2">Instance Name</th>'' + N''<th><font face="Verdana" size="2">Database Name</th>'' + N''<th><font face="Verdana" size="2">Backup Start Date</th>'' + N''<th><font face="Verdana" size="2">Backup Finish Date</th>'' + N''<th><font face="Verdana" size="2">Backup Time (secs)</th>'' + N''<th><font face="Verdana" size="2">Backup Size</th>'' + N''<th><font face="Verdana" size="2">Physical Device Name</th></tr>'' + CAST ( ( SELECT td = bus.instance, '''', td = bus.database_name, '''', td = bus.backup_start_date, '''', td = bus.backup_finish_date, '''', td = (((DATEPART(HH,bus.backup_finish_date))- (DATEPART(HH,bus.backup_start_date)))*3600) + (((DATEPART(MI,bus.backup_finish_date)) - (DATEPART(MI,bus.backup_start_date)))*60) + (((DATEPART(SS,bus.backup_finish_date)) - DATEPART(SS,bus.backup_start_date))), '''', td = bus.backup_size, '''', td = bus.physical_device_name FROM backuphistory as bus WHERE bus.backup_start_date >= (getdate() - 7) ORDER BY bus.backup_start_date desc FOR XML PATH(''tr''), TYPE ) AS NVARCHAR(MAX) ) + N''</table>'' + ''<p style="margin-top: 0; margin-bottom: 0"> </p> <p style="margin-top: 0; margin-bottom: 0"><font face="Verdana" size="2">Thanks and Regards,</font></p> <p style="margin-top: 0; margin-bottom: 0"><font face="Verdana" size="2">Enterprise Database Operations</font></p> <p> </p>'' ; EXEC msdb.dbo.sp_send_dbmail @recipients=''[email protected];[email protected]'', @subject = ''Enterprise Backup History Summary'', @body = @tableHTML , @body_format = ''HTML'' ;', @database_name=N'msdb', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [Mail Complete Result Set to Support] Script Date: 9/26/2018 10:16:46 PM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @[email protected], @step_name=N'Mail Complete Result Set to Support', @step_id=3, @cmdexec_success_code=0, @on_success_action=1, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N'DECLARE @tableHTML NVARCHAR(MAX) ; SET @tableHTML = N''<H1><font face="Verdana" size="4">Enterprise Backup History Summary</H1>'' + N''<table border="1"><font face="Verdana" size="2">'' + N''<tr><th><font face="Verdana" size="2">Instance Name</th>'' + N''<th><font face="Verdana" size="2">Database Name</th>'' + N''<th><font face="Verdana" size="2">Backup Start Date</th>'' + N''<th><font face="Verdana" size="2">Backup Finish Date</th>'' + N''<th><font face="Verdana" size="2">Backup Time (secs)</th>'' + N''<th><font face="Verdana" size="2">Backup Size</th>'' + N''<th><font face="Verdana" size="2">Physical Device Name</th></tr>'' + CAST ( ( SELECT td = bus.instance, '''', td = bus.database_name, '''', td = bus.backup_start_date, '''', td = bus.backup_finish_date, '''', td = (((DATEPART(HH,bus.backup_finish_date))- (DATEPART(HH,bus.backup_start_date)))*3600) + (((DATEPART(MI,bus.backup_finish_date)) - (DATEPART(MI,bus.backup_start_date)))*60) + (((DATEPART(SS,bus.backup_finish_date)) - DATEPART(SS,bus.backup_start_date))), '''', td = bus.backup_size, '''', td = bus.physical_device_name FROM backuphistory as bus WHERE bus.backup_start_date >= (getdate() - 7) ORDER BY bus.backup_start_date desc FOR XML PATH(''tr''), TYPE ) AS NVARCHAR(MAX) ) + N''</table>'' + ''<p style="margin-top: 0; margin-bottom: 0"> </p> <p style="margin-top: 0; margin-bottom: 0"><font face="Verdana" size="2">Thanks and Regards,</font></p> <p style="margin-top: 0; margin-bottom: 0"><font face="Verdana" size="2">Enterprise Database Operations</font></p> <p> </p>'' ; EXEC msdb.dbo.sp_send_dbmail @recipients=''[email protected];[email protected]'', @subject = ''Enterprise Backup History Summary'', @body = @tableHTML , @body_format = ''HTML'' ;', @database_name=N'msdb', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION EndSave: GO
Wykonanie tego zadania daje wynik pokazany na Rys. 9. Tabela jest tworzona przy użyciu bardzo prostego kodu HTML i może być dalej rozwijana w celu dostosowania do Twoich potrzeb.
Rys 9. Dane wyjściowe e-maili wykonywania zadań SQL Agent
Wniosek
Przeszliśmy przez prostą metodę agregowania informacji o historii kopii zapasowych (i ewentualnie innych danych zawartych w bazach danych systemu) za pomocą serwerów połączonych. Następnie zautomatyzowaliśmy ten proces za pomocą agenta SQL, poczty bazy danych i niewielkiego kodu HTML. Ta metoda może wydawać się nieco prymitywna i jestem pewien, że istnieją narzędzia, które mogą działać znacznie lepiej, ale byłoby to przeznaczenie serwerowe dla tych, którzy dopiero zaczynają korzystać z SQL Server lub środowisk o niskim budżecie. Przy odrobinie kreatywności możesz dalej dostosowywać skrypty i dostosowywać je do innych zastosowań.
Referencje
- Konfigurowanie poczty bazy danych
- Pierwsze kroki z Amazon SES
- Serwery połączone
- Historia kopii zapasowej i informacje nagłówka