Wprowadzenie
Transaction Log Shipping to bardzo dobrze znana technologia wykorzystywana w SQL Server do utrzymywania kopii działającej bazy danych w witrynie Disaster Recovery Site. Technologia zależy od trzech kluczowych zadań:zadania kopii zapasowej, zadania kopiowania i zadania przywracania. Podczas gdy zadanie kopii zapasowej działa na serwerze głównym, zadania kopiowania i przywracania działają na serwerze dodatkowym. Zasadniczo proces obejmuje okresowe kopie zapasowe dziennika transakcji do udziału, z którego zadanie kopiowania jest przenoszone na serwer pomocniczy; następnie zadanie przywracania stosuje kopie zapasowe dziennika na serwerze pomocniczym. Zanim to wszystko się zacznie, dodatkowa baza danych musi zostać zainicjowana pełną kopią zapasową z serwera głównego przywróconą z opcją NORECOVERY.
Firma Microsoft udostępnia zestaw procedur składowanych, których można użyć do kompleksowego skonfigurowania przesyłania dzienników, a także odpowiedników GUI, zaczynając od elementu właściwości każdej bazy danych, dla której możesz chcieć skonfigurować przesyłanie dzienników. Warto zauważyć, że Secondary Database można skonfigurować w trybie NORECOVERY lub w trybie STANDBY. W trybie NORECOVERY baza danych nigdy nie jest dostępna dla zapytań, ale w trybie STANDBY, dodatkowa baza danych może być sprawdzana, gdy nie jest wykonywana żadna operacja przywracania dziennika transakcji.
Konfigurowanie środowiska
Aby nabrać tempa, tworzymy dwie instancje SQL Server na AWS z identycznym obrazem Amazon EC2. Ta instancja Amazon EC2 działa pod kontrolą SQL Server 2017 RTM-CU5 w systemie Windows Server 2016. Następnie przywracamy kopię bazy danych WideWorldImporters przy użyciu zestawu kopii zapasowych pobranego z GitHub do pierwszej instancji, naszej podstawowej instancji. Używamy tego samego zestawu kopii zapasowych do tworzenia dwóch identycznych baz danych o nazwach BranchDB i CorporateDB.
Rys. 1 wersja serwera SQL
Rys. 2 BranchDB i CorporateDB w instancji podstawowej (pusta instancja dodatkowa)
Listing 1:Przywracanie przykładowej bazy danych WideWorldImporters
restore filelistonly from disk='WideWorldImporters-Full.bak' restore database CorporateDB from disk='WideWorldImporters-Full.bak' with stats=10,recovery, move 'WWI_Primary' to 'M:\MSSQL\Data\WWI_Primary.mdf' , move 'WWI_UserData' to 'M:\MSSQL\Data\WWI_UserData.ndf' , move 'WWI_Log' to 'N:\MSSQL\Log\WWI_Log.ldf', move 'WWI_InMemory_Data_1' to 'M:\MSSQL\Data\WWI_InMemory_Data_1.ndf' restore database BranchDB from disk='WideWorldImporters-Full.bak' with stats=10,recovery, move 'WWI_Primary' to 'M:\MSSQL\Data\WWI_Primary1.mdf' , move 'WWI_UserData' to 'M:\MSSQL\Data\WWI_UserData1.ndf' , move 'WWI_Log' to 'N:\MSSQL\Log\WWI_Log1.ldf', move 'WWI_InMemory_Data_1' to 'M:\MSSQL\Data\WWI_InMemory_Data_11.ndf
Mamy teraz dwie instancje, podstawową instancję hostującą dwie podstawowe bazy danych (BranchDB i CorporateDB oraz drugą instancję bez baz danych użytkowników. Kontynuujemy konfigurowanie przesyłania dziennika transakcji w obu bazach danych, ale różnicujemy je, stosując opóźnienie do konfiguracji przywracania pierwszą bazę danych. Przypomnij sobie, że bazy danych są w rzeczywistości identyczne pod względem zawartych w nich danych. Poniższa grafika przedstawia kluczowe opcje wybrane w konfiguracji przesyłania dziennika.
Rys. 3 ustawienia kopii zapasowej dla BranchDB
Rys. 4 Kopiuj ustawienia dla BranchDB
Rys. 5 Przywróć ustawienia dla BranchDB
Każde zadanie Log Shipping jest skonfigurowane do uruchamiania co pięć minut. Aby przetworzyć „Opóźnione przywracanie kopii zapasowych”, musimy użyć trybu przywracania w trybie gotowości w konfiguracji Log Shipping. Jest to logiczne, ponieważ ma drugorzędną bazę danych w trybie gotowości i wskazuje, że możemy wysyłać zapytania do pomocniczej bazy danych, gdy przywracanie dziennika transakcji nie jest w toku. Wartość określona w tej opcji (w tym przypadku 30 minut) daje nam dobre okno, podczas którego możemy generować raporty z drugorzędnej bazy danych, oprócz podstawowego wymagania tego artykułu, czyli możliwości odzyskania po błędzie użytkownika.
Powinniśmy również wspomnieć, że przywracanie kopii zapasowych dziennika transakcji faktycznie jest opóźnione. Jego sygnatura czasowa jest późniejsza niż wartość opóźnienia. Oznacza to, że wszystkie kopie zapasowe dziennika transakcji zostaną skopiowane na serwer pomocniczy, który jest oparty na harmonogramie i określonym w zadaniu kopiowania. W rzeczywistości zadanie przywracania będzie nadal działać zgodnie z harmonogramem, ale kopie zapasowe dziennika transakcji (nie starsze niż 30 minut) nie zostaną przywrócone. Zasadniczo baza danych BranchDB w stanie gotowości jest 30 minut za podstawową bazą danych BranchDB. Aby zademonstrować to opóźnienie, w następnej sekcji stworzymy tabelę w obu bazach danych i stworzymy zadanie, które wstawia rekord co minutę. Zbadamy tę tabelę w drugorzędnych bazach danych.
Ustawienia bazy danych CorporateDB są takie same jak na rysunkach. 3 do 5, z wyjątkiem zadania przywracania, które NIE jest ustawione na opóźnienie tworzenia kopii zapasowych dziennika transakcji.
Rys. 6 Przywróć ustawienia dla CorporateDB
Weryfikacja konfiguracji
Po zakończeniu konfiguracji możemy sprawdzić, czy konfiguracja jest w porządku i rozpocząć obserwację jej pracy. Raport dotyczący wysyłki dziennika transakcji pokazuje nam, że Branch DB rzeczywiście opóźnia CorporateDB pod względem przywracania:
Rys. 7a Raport wysyłki dziennika transakcji na serwerze głównym
Rys. 7b Raport wysyłki dziennika transakcji na serwerze pomocniczym
Dodatkowo zauważysz poniższy komunikat w historii zadań przywracania dla BranchDB:
Rys. 8 przywracania pominiętych dzienników transakcji na serwerze pomocniczym
Możemy przejść dalej z tą weryfikacją, tworząc tabelę i używając zadania do zapełniania tej tabeli wierszami co minutę. Zadanie to prosty sposób na symulację tego, co aplikacja może robić z tabelą użytkowników. To może nam pokazać, że to opóźnienie jest zdecydowanie widoczne w danych użytkownika.
Listing 2 – Utwórz tabelę śledzenia dziennika
use BranchDB go create table log_ship_tracker ( ID int identity (100,1) ,Database_Name sysname default db_name() ,RecordTime datetime default getdate() ,ServerName sysname default @@servername) use CorporateDB go create table log_ship_tracker ( ID int identity (100,1) ,Database_Name sysname default db_name() ,RecordTime datetime default getdate() ,ServerName sysname default @@servername)
Listing 3 – Utwórz zadanie w celu wypełnienia tabeli śledzenia dziennika
/* ==Scripting Parameters== Source Server Version : SQL Server 2017 (14.0.3023) Source Database Engine Edition : Microsoft SQL Server Standard Edition Source Database Engine Type : Standalone SQL Server Target Server Version : SQL Server 2017 Target Database Engine Edition : Microsoft SQL Server Standard Edition Target Database Engine Type : Standalone SQL Server */ USE [msdb] GO /****** Object: Job [InsertRecords] Script Date: 7/2/2018 3:32:00 PM ******/ BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 /****** Object: JobCategory [[Uncategorized (Local)]] Script Date: 7/2/2018 3:32:00 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'InsertRecords', @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'kairos\kigiri', @job_id = @jobId OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [InsertRecords] Script Date: 7/2/2018 3:32:00 PM ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @[email protected], @step_name=N'InsertRecords', @step_id=1, @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'use BranchDB go insert into log_ship_tracker values (db_name(),getdate(),@@servername) use CorporateDB go insert into log_ship_tracker values (db_name(),getdate(),@@servername) GO', @database_name=N'master', @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_jobschedule @[email protected], @name=N'Schedule', @enabled=1, @freq_type=4, @freq_interval=1, @freq_subday_type=4, @freq_subday_interval=1, @freq_relative_interval=0, @freq_recurrence_factor=0, @active_start_date=20180702, @active_end_date=99991231, @active_start_time=0, @active_end_time=235959, @schedule_uid=N'03e5f1b2-2e0b-4b30-8d60-3643c84aa08d' 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
Gdy wysyłamy zapytanie do tabeli odpowiednio do podstawowych baz danych, możemy potwierdzić (za pomocą kolumny RecordTime), że wiersze są zgodne w BranchDB i CorporateDB. Kiedy badamy tabelę w drugorzędnych bazach danych, w ten sam sposób widzimy wyraźnie, że mamy 30-minutową przerwę między bazą danych BranchDB a bazą danych korporacji.
Listing 4 – Odpytywanie tabeli śledzenia dziennika
select top 10 @@servername [Current_Server],* from BranchDB.dbo.log_ship_tracker order by RecordTime desc select top 10 @@servername [Current_Server], * from CorporateDB.dbo.log_ship_tracker order by RecordTime desc
Rys. 9 tabel śledzenia dzienników pasuje do podstawowych baz danych
Rys. 10 tabel śledzenia dzienników ma około 30-minutową przerwę w drugorzędnych bazach danych
Przywracanie po błędzie użytkownika
Porozmawiajmy teraz o kluczowej korzyści tego opóźnienia. W scenariuszu, w którym użytkownik przypadkowo upuszcza tabelę, możemy szybko odzyskać dane z pomocniczej bazy danych, o ile nie upłynął okres opóźnienia. W tym przykładzie usuwamy tabelę Sales.Orderlines w OBU bazach danych i sprawdzamy, czy tabela nie istnieje już w OBU bazach danych.
Listing 5 – Porzucanie tabeli linii zamówień
drop table BranchDB.Sales.Orderlines drop table CorporateDB.Sales.Orderlines GO use BranchDB go select @@servername [Current_Server] , db_name() [Database_Name] , name , schema_name(schema_id) [schema] , type_desc , create_date , modify_date from sys.tables where name='Orderlines' GO use CorporateDB go select @@servername [Current_Server] , db_name() [Database_Name] , name , schema_name(schema_id) [schema] , type_desc , create_date , modify_date from sys.tables where name='Orderlines' GO
Rys. 11 Dropping Table Sales.Orderlines
Kiedy szukamy tabeli na serwerze pomocniczym, stwierdzamy, że tabela jest nadal dostępna w OBU bazach danych. Tak więc w przypadku CorporateDB mamy mniej niż pięć minut na odzyskanie danych. (ryc. 12). Ale po wykonaniu kolejnego przywracania Cycle, tracimy tabelę w bazie danych Corporate DB. Aby odzyskać tę tabelę, musimy wykonać odzyskiwanie do określonego momentu przy użyciu pełnej kopii zapasowej w osobnym środowisku, a następnie wyodrębnić tę konkretną tabelę. Zgodzisz się, że zajmie to trochę czasu. W przypadku tabeli Linie zamówień BranchDB mamy trochę więcej czasu i możemy odzyskać tabelę za pomocą pojedynczej instrukcji SQL przez połączony serwer (patrz Listing 6).
Rys. Odliczanie 12 minut:tabela istnieje w obu drugorzędnych bazach danych
Rys. 13 dodatkowych 25 minut na odzyskanie tabeli BranchDB
Listing 6 – Odzyskaj tabelę linii zamówień
USE [master] GO /****** Object: LinkedServer [10.2.1.84] Script Date: 7/2/2018 4:14:59 PM ******/ EXEC master.dbo.sp_addlinkedserver @server = N'10.2.1.84', @srvproduct=N'SQL Server' /* For security reasons the linked server remote logins password is changed with ######## */ EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'10.2.1.84',@useself=N'True',@locallogin=NULL,@rmtuser=NULL,@rmtpasswo rd=NULL GO select * into BranchDB.Sales.Orderlines from [10.2.1.84].BranchDB.Sales.Orderlines
Rys. 14 Odzyskaj tabelę BranchDB Sales.Orderlines
Następnie weryfikujemy serwer główny (bazę danych BranchDB), czy tabela została przywrócona.
Rys. 15 Odzyskaj tabelę BranchDB Sales.Orderlines
Wniosek
SQL Server zapewnia wiele sposobów odzyskiwania danych po utracie danych z różnych przyczyn źródłowych – awarii dysku, uszkodzenia, błędu użytkownika itp. Odzyskiwanie do określonego momentu z kopii zapasowych jest prawdopodobnie najbardziej znaną z tych metod. W przypadku niektórych prostych przypadków błędu użytkownika lub podobnych przypadków, w których jeden lub dwa obiekty są tracone, dobrym rozwiązaniem jest użycie funkcji przesyłania dziennika transakcji z opóźnionym odzyskiwaniem. Należy jednak zauważyć, że dodatkowa baza danych, skonfigurowana wyłącznie na potrzeby DR, musi być wybrana dla niższych wartości RPO.