Database
 sql >> база данни >  >> RDS >> Database

Четени второстепенни материали на бюджет

Групите за наличност, въведени в SQL Server 2012, представляват фундаментална промяна в начина, по който мислим както за висока наличност, така и за възстановяване при бедствие за нашите бази данни. Едно от страхотните неща, които стават възможни тук, е разтоварването на операции само за четене към вторична реплика, така че първичният екземпляр за четене/запис да не се притеснява от досадни неща като отчитане на крайния потребител. Настройването на това не е лесно, но е много по-лесно и по-поддържащо се от предишните решения (вдигнете ръка, ако ви харесва да настройвате огледални снимки и моментни снимки, както и цялата постоянна поддръжка, свързана с това).

Хората се вълнуват много, когато чуят за Групи за наличност. Тогава реалността настъпва:функцията изисква Enterprise Edition на SQL Server (все пак от SQL Server 2014). Enterprise Edition е скъпо, особено ако имате много ядра, и особено след премахването на базираното на CAL лицензиране (освен ако не сте били наследени от 2008 R2, в който случай сте ограничени до първите 20 ядра). Той също така изисква Windows Server Failover Clustering (WSFC), усложнение не само за демонстриране на технологията на лаптоп, но също така изисква Enterprise Edition на Windows, контролер на домейн и цял куп конфигурации за поддръжка на клъстериране. Има и нови изисквания около Software Assurance; допълнителна цена, ако искате вашите екземпляри в режим на готовност да бъдат съвместими.

Някои клиенти не могат да оправдаят цената. Други виждат стойността, но просто не могат да си го позволят. И така, какво трябва да правят тези потребители?

Вашият нов герой:Доставка на трупи

Доставката на трупи съществува от векове. Това е просто и просто работи. Почти винаги. И освен заобикалянето на разходите за лицензиране и препятствията за конфигурация, представени от Availability Groups, може да се избегне и 14-байтовата санкция, за която Пол Рандъл (@PaulRandal) говори в тазседмичния бюлетин SQLskills Insider (13 октомври 2014 г.).

Едно от предизвикателствата, с които хората се сблъскват с използването на изпратеното копие на регистрационния файл като четливо вторично, обаче е, че трябва да изхвърлите всички текущи потребители, за да приложите нови регистрационни файлове – така че потребителите или ще се дразнят, защото са многократно нарушавани от изпълнение на заявки, или имате потребители да се дразнят, защото техните данни са остарели. Това е така, защото хората се ограничават до един четлив вторичен елемент.

Не е задължително да е така; Мисля, че тук има изящно решение и макар че може да изисква много повече работа на краката предварително, отколкото, да речем, включването на групи за наличност, със сигурност ще бъде привлекателна опция за някои.

По принцип можем да настроим няколко вторични, където ще регистрираме кораба и ще направим само един от тях „активен“ вторичен, като използваме кръгов подход. Задачата, която изпраща регистрационните файлове, знае кой е активен в момента, така че възстановява само нови регистрационни файлове на "следващия" сървър с помощта на WITH STANDBY опция. Приложението за отчитане използва същата информация, за да определи по време на изпълнение какъв трябва да бъде низът за връзка за следващия отчет, който потребителят стартира. Когато следващото архивиране на регистрационния файл е готово, всичко се измества с едно и екземплярът, който сега ще стане новият четим вторичен, се възстановява с помощта на WITH STANDBY .

За да запазим модела неусложнен, да кажем, че имаме четири екземпляра, които служат като четими вторични, и правим резервни копия на регистрационни файлове на всеки 15 минути. Във всеки един момент ще имаме един активен вторичен източник в режим на готовност, с данни, не по-стари от 15 минути, и три вторични в режим на готовност, които не обслужват нови заявки (но все още може да връщат резултати за по-стари заявки).

Това ще работи най-добре, ако не се очаква заявките да продължат повече от 45 минути. (Може да се наложи да коригирате тези цикли в зависимост от естеството на вашите операции само за четене, колко едновременни потребители изпълняват по-дълги заявки и дали някога е възможно да нарушите потребителите, като изгоните всички.)

Също така ще работи най-добре, ако последователни заявки, изпълнявани от един и същ потребител, могат да променят своя низ за връзка (това е логика, която трябва да бъде в приложението, въпреки че можете да използвате синоними или изгледи в зависимост от архитектурата) и съдържат различни данни, които имат се промени междувременно (точно както ако искаха жива, постоянно променяща се база данни).

Имайки предвид всички тези предположения, ето илюстративна последователност от събития за първите 75 минути от нашето внедряване:

време събития визуално
12:00 (t0)
  • Резервно копие на дневника t0
  • Изхвърляне на потребители от екземпляр A
  • Възстановяване на log t0 към екземпляр A (STANDBY)
  • Новите заявки само за четене ще отидат в екземпляр A
12:15 (t1)
  • Резервно копие на дневника t1
  • Изхвърляне на потребители от екземпляр B
  • Възстановяване на log t0 в екземпляр B (NORECOVERY)
  • Възстановете регистрационния файл t1 към екземпляр B (STANDBY)
  • Новите заявки само за четене ще отидат в екземпляр B
  • Съществуващите заявки само за четене към екземпляр А могат да продължат да се изпълняват, но около 15 минути назад
12:30 (t2)
  • Резервно копие на дневника t2
  • Изхвърляне на потребители от екземпляр C
  • Възстановяване на регистрационни файлове t0 -> t1 към екземпляр C (NORECOVERY)
  • Възстановете регистрационния файл t2 в екземпляр C (STANDBY)
  • Новите заявки само за четене ще отидат в екземпляр C
  • Съществуващите заявки само за четене към екземпляри A и B могат да продължат да се изпълняват (15-30 минути назад)
12:45 (t3)
  • Резервно копие на дневника t3
  • Изхвърляне на потребители от екземпляр D
  • Възстановяване на регистрационни файлове t0 -> t2 към екземпляр D (NORECOVERY)
  • Възстановете регистрационния файл t3 към екземпляр D (STANDBY)
  • Новите заявки само за четене ще отидат в екземпляр D
  • Съществуващите заявки само за четене към екземпляри A, B и C могат да продължат да се изпълняват (15-45 минути назад)
13:00 (t4)
  • Резервно копие на дневника t4
  • Изхвърляне на потребители от екземпляр A
  • Възстановяване на регистрационни файлове t1 -> t3 към екземпляр A (NORECOVERY)
  • Възстановете регистрационния файл t4 към екземпляр A (STANDBY)
  • Новите заявки само за четене ще отидат в екземпляр A
  • Съществуващите заявки само за четене към екземпляри B, C и D могат да продължат да се изпълняват (15-45 минути назад)
  • Заявките, които все още се изпълняват на екземпляр A от t0 -> ~t1 (45-60 минути) ще бъдат анулирани


Това може да изглежда достатъчно просто; писането на кода за справяне с всичко това е малко по-трудно. Приблизителен контур:

  1. На основния сървър (ще го нарека BOSS ), създайте база данни. Преди дори да помислите да продължите по-нататък, включете Trace Flag 3226, за да предотвратите успешните архивни съобщения от замърсяване на регистъра за грешки на SQL Server.
  2. На BOSS , добавете свързан сървър за всеки вторичен (ще ги нарека PEON1 -> PEON4 ).
  3. Някъде, достъпно за всички сървъри, създайте споделяне на файлове за съхраняване на резервни копия на база данни/регистрационни файлове и се уверете, че акаунтите на услугите за всеки екземпляр имат достъп за четене/запис. Освен това всеки вторичен екземпляр трябва да има определено местоположение за файла в режим на готовност.
  4. В отделна помощна база данни (или MSDB, ако предпочитате), създайте таблици, които ще съдържат конфигурационна информация за базата(ите), всички вторични данни и хронологията на архивиране и възстановяване на регистрационни файлове.
  5. Създайте съхранени процедури, които ще архивират базата данни и ще възстановят във вторичните С NORECOVERY и след това приложете един регистрационен файл С РЕЖИМ НА РЕЖИМ и маркирайте един екземпляр като текущ вторичен режим на готовност. Тези процедури могат да се използват и за повторно инициализиране на цялата настройка за доставка на регистрационни файлове, в случай че нещо се обърка.
  6. Създайте задание, което ще се изпълнява на всеки 15 минути, за да изпълнява описаните по-горе задачи:
    • резервно копие на дневника
    • определете към коя вторична база да приложите неприложените архивни копия на регистрационни файлове
    • възстановете тези регистрационни файлове с подходящите настройки
  7. Създайте съхранена процедура (и/или изглед?), която ще каже на извикващото приложение(а) кой вторичен елемент трябва да използва за всякакви нови заявки само за четене.
  8. Създайте процедура за почистване, за да изчистите хронологията на архивиране на регистрационни файлове, които са били приложени към всички вторични (и може би също да преместите или изчистите самите файлове).
  9. Допълнете решението със стабилна обработка на грешки и известия.

Стъпка 1 – създайте база данни

Основният ми екземпляр е Standard Edition, наречен .\BOSS . В този случай създавам проста база данни с една таблица:

ИЗПОЛЗВАЙТЕ [главен];GOCREATE DATABASE UserData;GOALTER DATABASE UserData SET RECOVERY FULL;GOUSE UserData;GOCREATE TABLE dbo.LastUpdate(EventTime DATETIME2);INSERT dbo.LastUpdate(EventTime(EventTime) SELECT);
SYS>

След това създавам задание на SQL Server Agent, което просто актуализира това клеймо за време всяка минута:

АКТУАЛИЗИРАНЕ на UserData.dbo.LastUpdate SET EventTime =SYSDATETIME();

Това просто създава първоначалната база данни и симулира дейност, което ни позволява да потвърдим как задачата за доставка на регистрационни файлове се върти през всеки от четливите вторични елементи. Искам да заявя изрично, че целта на това упражнение не е да стрес тестваме транспортирането на дневници или да докажем колко обем можем да пробием; това е съвсем различно упражнение.

Стъпка 2 – добавете свързани сървъри

Имам четири вторични екземпляра на Express Edition с име .\PEON1 , .\PEON2 , .\PEON3 и .\PEON4 . Така че изпълних този код четири пъти, променяйки @s всеки път:

ИЗПОЛЗВАЙТЕ [главен];GODECLARE @s NVARCHAR(128) =N'.\PEON1', -- повторете за .\PEON2, .\PEON3, .\PEON4 @t NVARCHAR(128) =N'true'; EXEC [master].dbo.sp_addlinkedserver @server =@s, @srvproduct =N'SQL Server';EXEC [master].dbo.sp_addlinkedsrvlogin @rmtsrvname =@s, @useself =@t;EXEC [master].dbo. sp_serveroption @server =@s, @optname =N'collation compatible', @optvalue =@t;EXEC [master].dbo.sp_serveroption @server =@s, @optname =N'достъп до данни', @optvalue =@t;EXEC [master].dbo.sp_serveroption @server =@s, @optname =N'rpc', @optvalue =@t;EXEC [master].dbo.sp_serveroption @server =@s, @optname =N'rpc out. ', @optvalue =@t;

Стъпка 3 – валидиране на споделяне(и) на файлове

В моя случай всичките 5 екземпляра са на един и същ сървър, така че току-що създадох папка за всеки екземпляр:C:\temp\Peon1\ , C:\temp\Peon2\ , и така нататък. Не забравяйте, че ако вашите вторични сървъри са на различни сървъри, местоположението трябва да е относително към този сървър, но все пак да бъде достъпно от основния (така че обикновено ще се използва UNC път). Трябва да потвърдите, че всеки екземпляр може да пише в този споделен ресурс и също така трябва да потвърдите, че всеки екземпляр може да записва на местоположението, посочено за файла в режим на готовност (използвах същите папки за режим на готовност). Можете да потвърдите това, като архивирате малка база данни от всеки екземпляр във всяко от посочените му местоположения – не продължавайте, докато това не работи.

Стъпка 4 – създаване на таблици

Реших да поставя тези данни в msdb , но всъщност нямам силни чувства за или против създаването на отделна база данни. Първата таблица, от която се нуждая, е тази, която съдържа информация за базата(ите), които ще изпращам:

CREATE TABLE dbo.PMAG_Databases( DatabaseName SYSNAME, LogBackupFrequency_Minutes SMALLINT NOT NULL DEFAULT (15), CONSTRAINT PK_DBS PRIMARY KEY(DatabaseName));GO INSERT dbo.PMAG_Databases'Databases'Upre; 

(Ако сте любопитни относно схемата за именуване, PMAG означава „групи за наличност на бедните хора.“)

Друга необходима таблица е такава, която съдържа информация за вторичните, включително техните отделни папки и текущото им състояние в последователността за изпращане на регистрационни файлове.

СЪЗДАВАНЕ НА ТАБЛИЦА dbo.PMAG_Secondaries( DatabaseName SYSNAME, ServerInstance SYSNAME, CommonFolder VARCHAR(512) NOT NULL, DataFolder VARCHAR(512) NOT NULL, LogFolder VARCHAR(512) NOT VARCHAR(512) NOT VARCHAR(512) NOT NULL NULL от NOTByLourtan2 NULL ПО ПОДРАЗБИРАНЕ 0, ОГРАНИЧЕНИЕ PK_Sec ПРАВИЛЕН КЛЮЧ(Име на база данни, ServerInstance), ОГРАНИЧЕНИЕ FK_Sec_DBs ВЪНШЕН КЛЮЧ(Име на база данни) REFERENCES dbo.PMAG_Databases(DatabaseName));

Ако искате да направите резервно копие от изходния сървър локално и вторичните да бъдат възстановени от разстояние или обратно, можете да разделите CommonFolder в две колони (BackupFolder и RestoreFolder ) и направете съответните промени в кода (няма да са толкова много).

Тъй като мога да попълня тази таблица въз основа поне частично на информацията в sys.servers – възползвайки се от факта, че данните/регистрационните файлове и другите папки са наименувани на имената на екземпляра:

INSERT dbo.PMAG_Secondaries( DatabaseName, ServerInstance, CommonFolder, DataFolder, LogFolder, StandByLocation)SELECT DatabaseName =N'UserData', ServerInstance =име, CommonFolder ='C:\temp\Peon' + RIGHT (име, 1) '\', DataFolder ='C:\Program Files\Microsoft SQL Server\MSSQL12.PEON' + RIGHT(име, 1) + '\MSSQL\DATA\', LogFolder ='C:\Program Files\Microsoft SQL Server\ MSSQL12.PEON' + RIGHT(име, 1) + '\MSSQL\DATA\', StandByLocation ='C:\temp\Peon' + RIGHT(име, 1) + '\' ОТ sys.servers КЪДЕТО име LIKE N' .\PEON[1-4]';

Имам нужда и от таблица за проследяване на отделни архивни копия на регистрационни файлове (не само последното), защото в много случаи ще трябва да възстановя множество регистрационни файлове в последователност. Мога да получа тази информация от msdb.dbo.backupset , но е много по-сложно да се получат неща като местоположението – и може да нямам контрол върху други задачи, които могат да изчистят историята на архивирането.

CREATE TABLE dbo.PMAG_LogBackupHistory( DatabaseName SYSNAME, ServerInstance SYSNAME, BackupSetID INT NOT NULL, Location VARCHAR(2000) NOT NULL, BackupTime DATETIME NOT NULL DEFAULT SYSDATEInstance SYSNAME(),KeDa. FK_LBH_DBs ВЪНШЕН КЛЮЧ(Име на база данни) РЕФЕРЕНЦИИ dbo.PMAG_Databases(Име на база данни), ОГРАНИЧЕНИЕ FK_LBH_Sec ВЪНШЕН КЛЮЧ(Име на база данни, екземпляр на сървър) REFERENCES dbo.PMAG_Secondaries(Database>ServerInstance)); 

Може да си помислите, че е разточително да съхранявате ред за всеки вторичен и да съхранявате местоположението на всеки архив, но това е за защита в бъдещето – за справяне със случая, когато премествате CommonFolder за всеки вторичен.

И накрая история на възстановяването на регистрационни файлове, така че във всеки един момент мога да видя кои регистрационни файлове са били възстановени и къде, а задачата за възстановяване може да бъде сигурна, че възстановява само регистрационни файлове, които все още не са били възстановени:

 Създаване на таблица DBO.PMAG_LOGRESTOREHISTORY (име на име на база данни, име на сървъри, име, backupsetId int, restoretime dateTime, ограничение pk_lrh първичен ключ (база данни, име на сървър, резервно копие), constrain ВЪНШЕН КЛЮЧ(Име на база данни, екземпляр на сървър) РЕФЕРЕНЦИИ dbo.PMAG_Secondaries(Име на база данни, ServerInstance));

Стъпка 5 – инициализиране на вторични елементи

Нуждаем се от съхранена процедура, която ще генерира архивен файл (и ще го отразява на всички местоположения, изисквани от различни инстанции), и също така ще възстановим по един регистрационен файл към всеки вторичен, за да ги поставим всички в режим на готовност. В този момент всички те ще бъдат достъпни за заявки само за четене, но само една ще бъде „текущият“ режим на готовност във всеки един момент. Това е съхранената процедура, която ще обработва както пълно архивиране, така и архивиране на регистрационни файлове на транзакции; когато е поискано пълно архивиране и @init е настроен на 1, той автоматично инициализира повторно изпращането на регистрационни файлове.

СЪЗДАВАНЕ НА ПРОЦЕДУРА [dbo].[PMAG_Backup] @dbname SYSNAME, @type CHAR(3) ='bak', -- или 'trn' @init BIT =0 -- използва се само с 'bak'ASBEGIN SET NOCOUNT ON ON ON ON.; -- генериране на шаблон за име на файл DECLARE @now DATETIME =SYSDATETIME(); ДЕКЛАРИРАНЕ @fn NVARCHAR(256) =@dbname + N'_' + CONVERT(CHAR(8), @сега, 112) + RIGHT(REPLICATE('0',6) + CONVERT(VARCHAR(32), DATEDIFF(SECOND) , CONVERT(ДАТА, @сега), @сега)), 6) + N'.' + @type; -- генерирайте команда за архивиране с MIRROR TO за всяка отделна CommonFolder DECLARE @sql NVARCHAR(MAX) =N'BACKUP' + CASE @type WHEN 'bak' THEN N' DATABASE ' ELSE N' LOG ' END + QUOTENAME(@dbname) + ' ' + STUFF( (ИЗБЕРЕТЕ DISTINCT CHAR(13) + CHAR(10) + N' MIRROR TO DISK =''' + s.CommonFolder + @fn + '''' ОТ dbo.PMAG_Secondaries AS s WHERE s.DatabaseName =@dbname ЗА XML PATH(''), TYPE).value(N'.[1]',N'nvarchar(max)'),1,9,N'') + N' С ИМЕ =N'' ' + @dbname + CASE @type WHEN 'bak' THEN N'_PMAGFull' ELSE N'_PMAGLog' END + ''', INIT, FORMAT' + CASE WHEN LEFT(CONVERT(NVARCHAR(128), SERVERPROPERTY(N'Edition') )), 3) IN (N'Dev', N'Ent') THEN N', КОМПРЕСИЯ;' ДРУГО N';' КРАЙ; EXEC [master].sys.sp_executesql @sql; IF @type ='bak' И @init =1 -- инициализира изпращането на регистрационни файлове BEGIN EXEC dbo.PMAG_InitializeSecondaries @dbname =@dbname, @fn =@fn; END IF @type ='trn' BEGIN -- запишете факта, че сме архивирали дневник INSERT dbo.PMAG_LogBackupHistory ( DatabaseName, ServerInstance, BackupSetID, Location ) SELECT DatabaseName =@dbname, ServerInstance =s.ServerInstance, BackupSetID =MAX .backup_set_id), Location =s.CommonFolder + @fn FROM msdb.dbo.backupset AS b CROSS JOIN dbo.PMAG_Secondaries AS s WHERE b.name =@dbname + N'_PMAGLog' И s.DatabaseName =@dbname GROUP BY s. ServerInstance, s.CommonFolder + @fn; -- след като сме архивирали регистрационните файлове, -- ги възстановите на следващия вторичен EXEC dbo.PMAG_RestoreLogs @dbname =@dbname; КРАЙ.

Това от своя страна извиква две процедури, които бихте могли да извикате отделно (но най-вероятно няма да го направите). Първо, процедурата, която ще инициализира вторичните елементи при първо стартиране:

ПРОМЕНИ ПРОЦЕДУРА dbo.PMAG_InitializeSecondaries @dbname SYSNAME, @fn VARCHAR(512)ASBEGIN SET NOCOUNT ON; -- изчистете съществуващата история/настройки (тъй като това може да е повторно иницииране) DELETE dbo.PMAG_LogBackupHistory WHERE DatabaseName =@dbname; DELETE dbo.PMAG_LogRestoreHistory WHERE Име на база данни =@dbname; АКТУАЛИЗАЦИЯ dbo.PMAG_Secondaries SET IsCurrentStandby =0 WHERE DatabaseName =@dbname; ДЕКЛАРИРАНЕ @sql NVARCHAR(MAX) =N'', @files NVARCHAR(MAX) =N''; -- трябва да знаете имената на логическите файлове - може да са повече от две SET @sql =N'SELECT @files =(SELECT N'', MOVE N'''''' + име + '''''' В N ''''$'' + CASE [type] WHEN 0 THEN N''df'' WHEN 1 THEN N''lf'' END + ''$'''''' FROM ' + QUOTENAME(@dbname) + '.sys.database_files WHERE [type] IN (0,1) FOR XML PATH, TYPE).value(N''.[1]'',N''nvarchar(max)'');'; EXEC master.sys.sp_executesql @sql, N'@files NVARCHAR(MAX) OUTPUT', @files =@files OUTPUT; SET @sql =N''; -- възстановяване - необходими са физически пътеки на данни/регистрационни файлове за WITH MOVE -- това може да се провали, очевидно, ако тези пътища+имена вече съществуват за друг db SELECT @sql +=N'EXEC ' + QUOTENAME(ServerInstance) + N' .master.sys.sp_executesql N''ВЪЗСТАНОВЯВАНЕ НА БАЗА ДАННИ ' + QUOTENAME(@dbname) + N' ОТ ДИСКА =N''''' + CommonFolder + @fn + N'''''' + N' СЪС ЗАМЯНА, БЕЗ ВЪЗСТАНОВЯВАНЕ ' + REPLACE(REPLACE(@files, N'$df$', DataFolder + @dbname + N'.mdf'), N'$lf$', LogFolder + @dbname + N'.ldf'), N '''', N'''''') + N';'';' + CHAR(13) + CHAR(10) ОТ dbo.PMAG_Secondaries WHERE Име на база данни =@dbname; EXEC [master].sys.sp_executesql @sql; -- архивиране на дневник за тази база данни EXEC dbo.PMAG_Backup @dbname =@dbname, @type ='trn'; -- възстановяване на регистрационните файлове EXEC dbo.PMAG_RestoreLogs @dbname =@dbname, @PrepareAll =1;END

И след това процедурата, която ще възстанови регистрационните файлове:

СЪЗДАВАНЕ НА ПРОЦЕДУРА dbo.PMAG_RestoreLogs @dbname SYSNAME, @PrepareAll BIT =0ASBEGIN SET NOCOUNT ON; ДЕКЛАРИРАЙТЕ @StandbyInstance SYSNAME, @CurrentInstance SYSNAME, @BackupSetID INT, @Location VARCHAR(512), @StandByLocation VARCHAR(512), @sql NVARCHAR(MAX), @rn INT; -- вземете "следващия" екземпляр в режим на готовност SELECT @StandbyInstance =MIN(ServerInstance) FROM dbo.PMAG_Secondaries WHERE IsCurrentStandby =0 И ServerInstance> (SELECT ServerInstance FROM dbo.PMAG_Secondaries WHERE IsCurrentStandBy); АКО @StandbyInstance Е NULL -- или е последно, или повторно иницииране BEGIN SELECT @StandbyInstance =MIN(ServerInstance) FROM dbo.PMAG_Secondaries; END -- изведете този екземпляр нагоре и в STANDBY -- за всеки вход в logbackuphistory не в logrestorehistory:-- възстановите и го вмъкнете в logrestorehistory -- маркирайте последния като STANDBY -- ако @prepareAll е вярно, маркирайте всички останали като NORECOVERY -- в този случай трябва да има само един, но за всеки случай ДЕКЛАРИРАЙТЕ c CURSOR LOCAL FAST_FORWARD ЗА ИЗБОР bh.BackupSetID, s.ServerInstance, bh.Location, s.StandbyLocation, rn =ROW_NUMBER() НАД (РАЗДЕЛ ОТ s. ServerInstance ORDER BY bh.BackupSetID DESC) ОТ dbo.PMAG_LogBackupHistory КАТО bh INNER JOIN dbo.PMAG_Secondaries AS s ON bh.DatabaseName =s.DatabaseName AND bh.ServerInstance =s.SHERNameeInstance =s.SHERNamedInstance s W.SHERNamedInstance @PrepareAll WHEN 1 THEN s.ServerInstance ELSE @StandbyInstance END И НЕ СЪЩЕСТВУВА ( ИЗБЕРЕТЕ 1 ОТ dbo.PMAG_LogRestoreHistory КАТО rh КЪДЕ DatabaseName =@dbname И ServerInstance =s.ServerSBIDAND =s.ServerSBIDAND ackupSetID ) ПОРЪЧАЙ ПО СЛУЧАЙ s.ServerInstance WHEN @StandbyInstance THEN 1 ELSE 2 END, bh.BackupSetID; ОТВОРЕНО c; ИЗВЛЕЧВАНЕ c В @BackupSetID, @CurrentInstance, @Location, @StandbyLocation, @rn; ДОКОЕ @@FETCH_STATUS -1 ЗАПОЧВА -- изхвърлете потребителите - задайте на single_user и след това обратно на multi SET @sql =N'EXEC ' + QUOTENAME(@CurrentInstance) + N'.[master].sys.sp_executesql ' + 'N' 'АКО СЪЩЕСТВУВА (ИЗБЕРЕТЕ 1 ОТ sys.databases, КЪДЕТО име =N''''' + @dbname + ''''' И [състояние] 1) ЗАПОЧНЕТЕ ПРОМЕНЯНЕ НА БАЗА ДАННИ ' + QUOTENAME(@dbname) + N' SET SINGLE_USER ' + N'С НЕЗАБАВНО ОТМЕНЯНЕ; ALTER DATABASE ' + QUOTENAME(@dbname) + N' SET MULTI_USER; КРАЙ;'';'; EXEC [master].sys.sp_executesql @sql; -- възстановяване на дневника (в STANDBY, ако е последният):SET @sql =N'EXEC ' + QUOTENAME(@CurrentInstance) + N'.[master].sys.sp_executesql ' + N'N''RESTORE LOG ' + QUOTENAME(@dbname) + N' ОТ ДИСКА =N''''' + @Местоположение + N''''' С ' + СЛОВ, КОГАТО @rn =1 И (@CurrentInstance =@StandbyInstance ИЛИ @PrepareAll =1) ТОГАВА N'STANDBY =N''''' + @StandbyLocation + @dbname + N'.standby''''' ELSE N'NORECOVERY' END + N';'';'; EXEC [master].sys.sp_executesql @sql; -- запишете факта, че сме възстановили регистрационни файлове INSERT dbo.PMAG_LogRestoreHistory (DatabaseName, ServerInstance, BackupSetID, RestoreTime) SELECT @dbname, @CurrentInstance, @BackupSetID, SYSDATETIME(); -- маркирайте новия режим на готовност IF @rn =1 И @CurrentInstance =@StandbyInstance -- това е новият STANDBY BEGIN UPDATE dbo.PMAG_Secondaries SET IsCurrentStandby =CASE ServerInstance WHEN @StandbyInstance THEN 1 ELSE 0 END WHERE @dbname; END FETCH c В @BackupSetID, @CurrentInstance, @Location, @StandbyLocation, @rn; КРАЙ ЗАТВОРИ c; ОТМЕНИ c;END

(Знам, че е много код и много загадъчен динамичен SQL. Опитах се да бъда много либерален с коментарите; ако има нещо, с което имате проблеми, моля, уведомете ме.)>

Така че сега всичко, което трябва да направите, за да стартирате системата, е да направите две извиквания на процедури:

EXEC dbo.PMAG_Backup @dbname =N'UserData', @type ='bak', @init =1;EXEC dbo.PMAG_Backup @dbname =N'UserData', @type ='trn';

Сега трябва да видите всеки екземпляр с резервно копие на базата данни:

И можете да видите кой в ​​момента трябва да служи като режим на готовност само за четене:

ИЗБЕРЕТЕ ServerInstance, IsCurrentStandby ОТ dbo.PMAG_Secondaries WHERE DatabaseName =N'UserData';

Стъпка 6 – създайте задание, което архивира/възстановява регистрационни файлове

Можете да поставите тази команда в задание, което планирате за всеки 15 минути:

EXEC dbo.PMAG_Backup @dbname =N'UserData', @type ='trn';

Това ще измества активния вторичен елемент на всеки 15 минути и неговите данни ще бъдат с 15 минути по-свежи от предишния активен вторичен елемент. Ако имате няколко бази данни с различни графици, можете да създадете няколко задания или да планирате заданието по-често и да проверите dbo.PMAG_Databases таблица за всеки отделен LogBackupFrequency_Minutes стойност, за да определите дали трябва да стартирате архивирането/възстановяването за тази база данни.

Стъпка 7 – преглед и процедура, за да кажете на приложението кой режим на готовност е активен

CREATE VIEW dbo.PMAG_ActiveSecondariesAS SELECT DatabaseName, ServerInstance FROM dbo.PMAG_Secondaries WHERE IsCurrentStandby =1;GO CREATE PROCEDURE dbo.PMAG_GetActiveSecondary @dbname SYSNAMEASBEGIN SET;NO ИЗБЕРЕТЕ ServerInstance ОТ dbo.PMAG_ActiveSecondaries WHERE DatabaseName =@dbname;ENDGO

В моя случай също ръчно създадох обединяване на изглед във всички UserData бази данни, за да мога да сравня актуалността на данните на първичния с всеки вторичен.

СЪЗДАЙТЕ ИЗГЛЕД dbo.PMAG_CompareRecency_UserDataAS С x(ServerInstance, EventTime) AS ( SELECT @@SERVERNAME, EventTime FROM UserData.dbo.LastUpdate UNION ALL SELECT N'.\PEON1', EventTime FROM [.\PEON1bo]. .LastUpdate UNION ALL SELECT N'.\PEON2', EventTime FROM [.\PEON2].UserData.dbo.LastUpdate UNION ALL SELECT N'.\PEON3', EventTime FROM [.\PEON3].UserData.dbo.LastUpdate UNION ALL SELECT N'.\PEON4', EventTime FROM [.\PEON4].UserData.dbo.LastUpdate ) SELECT x.ServerInstance, s.IsCurrentStandby, x.EventTime, Age_Minutes =DATEDIFF(MINUTE, x.EventTime, SYSDATETIME() Age_Seconds =DATEDIFF(SECOND, x.EventTime, SYSDATETIME()) FROM x LEFT OUTER JOIN dbo.PMAG_Secondaries AS s ON s.ServerInstance =x.ServerInstance И s.DatabaseName =N'UserData';GO

Примерни резултати от уикенда:

SELECT [Сега] =SYSDATETIME(); ИЗБЕРЕТЕ ServerInstance, IsCurrentStandby, EventTime, Age_Minutes, Age_Seconds FROM dbo.PMAG_CompareRecency_UserData ORDER BY Age_Seconds DESC;

Стъпка 8 – процедура за почистване

Почистването на хронологията на архивирането и възстановяването на регистрационните файлове е доста лесно.

СЪЗДАВАНЕ НА ПРОЦЕДУРА dbo.PMAG_CleanupHistory @dbname SYSNAME, @DaysOld INT =7ASBEGIN SET NOCOUNT ON; ДЕКЛАРИРАНЕ @cutoff INT; -- това предполага, че архивирането на регистрационни файлове или -- е било успешно или неуспешно на всички вторични елементи SELECT @cutoff =MAX(BackupSetID) FROM dbo.PMAG_LogBackupHistory AS bh WHERE DatabaseName =@dbname И BackupTime  

Сега можете да добавите това като стъпка в съществуващото задание или можете да го планирате напълно отделно или като част от други процедури за почистване.

Ще оставя почистването на файловата система за друга публикация (и вероятно отделен механизъм като цяло, като PowerShell или C# – това обикновено не е нещото, което искате да прави T-SQL).

Стъпка 9 – увеличете решението

Вярно е, че може да има по-добра обработка на грешки и други тънкости тук, за да направи това решение по-пълно. Засега ще оставя това като упражнение за читателя, но смятам да разгледам последващи публикации, за да обясня подробно подобренията и усъвършенстванията на това решение.

Променливи и ограничения

Имайте предвид, че в моя случай използвах Standard Edition като основно и Express Edition за всички вторични. Бихте могли да отидете крачка по-далеч в бюджетната скала и дори да използвате Express Edition като основно – много хора смятат, че Express Edition не поддържа доставка на журнали, докато всъщност това е просто съветникът, който не присъства във версиите на Management Studio Express преди SQL Server 2012 Service Pack 1. Въпреки това, тъй като Express Edition не поддържа SQL Server Agent, би било трудно да го направите издател в този сценарий – ще трябва да конфигурирате собствен планировчик за извикване на съхранените процедури (C# приложение на командния ред, изпълнявано от Windows Task Scheduler, задачи PowerShell или задачи на SQL Server Agent на още един екземпляр). За да използвате Express от двата края, вие също трябва да сте сигурни, че вашият файл с данни няма да надвишава 10 GB и вашите заявки ще функционират добре с ограниченията на паметта, процесора и функциите на това издание. В никакъв случай не предполагам, че Express е идеален; Просто го използвах, за да демонстрирам, че е възможно да имате много гъвкави четими вторични файлове безплатно (или много близо до него).

Освен това тези отделни екземпляри в моя сценарий живеят на една и съща виртуална машина, но изобщо не е нужно да работи по този начин – можете да разпределите екземплярите на множество сървъри; или можете да отидете по другия път и да възстановите в различни копия на базата данни, с различни имена, на един и същи екземпляр. Тези конфигурации ще изискват минимални промени в това, което изложих по-горе. И колко бази данни ще възстановявате и колко често, зависи изцяло от вас – въпреки че ще има практична горна граница (където [средно време на заявка]> [брой вторични] x [интервал за архивиране на журнала] ).

И накрая, определено има някои ограничения с този подход. Неизчерпателен списък:

  1. Въпреки че можете да продължите да правите пълни резервни копия по свой собствен график, архивите на регистрационните файлове трябва да служат като единственият ви механизъм за архивиране на регистрационни файлове. Ако трябва да съхранявате резервните копия на регистрационните файлове за други цели, няма да можете да архивирате регистрационните файлове отделно от това решение, тъй като те ще пречат на веригата от регистрационни файлове. Вместо това можете да помислите за добавяне на допълнително MIRROR TO аргументи към съществуващите скриптове за архивиране на регистрационни файлове, ако трябва да имате копия на регистрационните файлове, използвани другаде.
  2. While "Poor Man's Availability Groups" may seem like a clever name, it can also be a bit misleading. This solution certainly lacks many of the HA/DR features of Availability Groups, including failover, automatic page repair, and support in the UI, Extended Events and DMVs. This was only meant to provide the ability for non-Enterprise customers to have an infrastructure that supports multiple readable secondaries.
  3. I tested this on a very isolated VM system with no concurrency. This is not a complete solution and there are likely dozens of ways this code could be made tighter; as a first step, and to focus on the scaffolding and to show you what's possible, I did not build in bulletproof resiliency. You will need to test it at your scale and with your workload to discover your breaking points, and you will also potentially need to deal with transactions over linked servers (always fun) and automating the re-initialization in the event of a disaster.

The "Insurance Policy"

Log shipping also offers a distinct advantage over many other solutions, including Availability Groups, mirroring and replication:a delayed "insurance policy" as I like to call it. At my previous job, I did this with full backups, but you could easily use log shipping to accomplish the same thing:I simply delayed the restores to one of the secondary instances by 24 hours. This way, I was protected from any client "shooting themselves in the foot" going back to yesterday, and I could get to their data easily on the delayed copy, because it was 24 hours behind. (I implemented this the first time a customer ran a delete without a where clause, then called us in a panic, at which point we had to restore their database to a point in time before the delete – which was both tedious and time consuming.) You could easily adapt this solution to treat one of these instances not as a read-only secondary but rather as an insurance policy. More on that perhaps in another post.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Trace Flag 2389 и новият оценител на мощността

  2. Поддържане на групирана работа MAX (или MIN)

  3. Създаване на нови таблици в IRI Workbench

  4. Мониторинг на регистъра на транзакциите

  5. Брой прочетени редове / действителни предупреждения за четене на редове в Plan Explorer