sql >> Database teknologi >  >> RDS >> Database

Læsbare sekundærer på et budget

Tilgængelighedsgrupper, introduceret i SQL Server 2012, repræsenterer et grundlæggende skift i den måde, vi tænker på både høj tilgængelighed og gendannelse af katastrofer for vores databaser. En af de fantastiske ting, der er muliggjort her, er at overføre skrivebeskyttede operationer til en sekundær replika, så den primære læse/skrive-instans ikke bliver generet af irriterende ting som slutbrugerrapportering. Det er ikke nemt at konfigurere dette, men det er meget nemmere og mere vedligeholdeligt end tidligere løsninger (ræk hånden op, hvis du kunne lide at opsætte spejling og snapshots, og al den evige vedligeholdelse, der er forbundet med det).

Folk bliver meget begejstrede, når de hører om tilgængelighedsgrupper. Så rammer virkeligheden:Funktionen kræver Enterprise Edition af SQL Server (ihvertfald fra SQL Server 2014). Enterprise Edition er dyrt, især hvis du har mange kerner, og især siden elimineringen af ​​CAL-baserede licenser (medmindre du blev grandfathered i fra 2008 R2, i hvilket tilfælde du er begrænset til de første 20 kerner). Det kræver også Windows Server Failover Clustering (WSFC), en komplikation, ikke kun for at demonstrere teknologien på en bærbar computer, men som også kræver Enterprise Edition af Windows, en domænecontroller og en hel masse konfigurationer for at understøtte klyngedannelse. Og der er også nye krav omkring Software Assurance; en ekstra omkostning, hvis du ønsker, at dine standby-instanser skal være kompatible.

Nogle kunder kan ikke retfærdiggøre prisen. Andre ser værdien, men har simpelthen ikke råd til det. Så hvad skal disse brugere gøre?

Din nye helt:Logforsendelse

Logforsendelse har eksisteret i evigheder. Det er enkelt og det virker bare. Næsten altid. Og bortset fra at omgå licensomkostningerne og konfigurationshindringerne præsenteret af Availability Groups, kan den også undgå den 14-byte straf, som Paul Randal (@PaulRandal) talte om i denne uges SQLskills Insider-nyhedsbrev (13. oktober 2014).

En af de udfordringer, folk har med at bruge den afsendte logkopi som en læsbar sekundær, er dog, at du skal sparke alle de nuværende brugere ud for at anvende nye logfiler - så enten har du brugere, der bliver irriterede, fordi de gentagne gange bliver forstyrret fra at køre forespørgsler, eller du har brugere, der bliver irriterede, fordi deres data er forældede. Dette skyldes, at folk begrænser sig til en enkelt læsbar sekundær.

Sådan behøver det ikke være; Jeg tror, ​​der er en yndefuld løsning her, og selvom det måske kræver meget mere benarbejde foran end for eksempel at slå tilgængelighedsgrupper til, vil det helt sikkert være en attraktiv mulighed for nogle.

Grundlæggende kan vi opsætte et antal sekundærer, hvor vi logger skib og gør kun en af ​​dem til den "aktive" sekundære, ved hjælp af en round-robin tilgang. Jobbet, der sender logfilerne, ved, hvilken der er aktiv i øjeblikket, så den gendanner kun nye logfiler til den "næste" server ved hjælp af WITH STANDBY mulighed. Rapporteringsapplikationen bruger de samme oplysninger til at bestemme ved kørsel, hvad forbindelsesstrengen skal være for den næste rapport, brugeren kører. Når den næste log-backup er klar, skifter alt med én, og den instans, der nu bliver den nye læsbare sekundære, gendannes ved hjælp af WITH STANDBY .

For at holde modellen ukompliceret, lad os sige, at vi har fire forekomster, der fungerer som læsbare sekundære, og vi tager log backups hvert 15. minut. På et hvilket som helst tidspunkt har vi én aktiv sekundær i standbytilstand, med data, der ikke er ældre end 15 minutter gamle, og tre sekundære i standbytilstand, der ikke betjener nye forespørgsler (men kan stadig returnere resultater for ældre forespørgsler).

Dette fungerer bedst, hvis ingen forespørgsler forventes at vare længere end 45 minutter. (Du skal muligvis justere disse cyklusser afhængigt af arten af ​​dine skrivebeskyttede operationer, hvor mange samtidige brugere der kører længere forespørgsler, og om det nogensinde er muligt at forstyrre brugere ved at sparke alle ud.)

Det vil også fungere bedst, hvis på hinanden følgende forespørgsler, der køres af den samme bruger, kan ændre deres forbindelsesstreng (dette er logik, der skal være i applikationen, selvom du kan bruge synonymer eller visninger afhængigt af arkitekturen), og indeholde forskellige data, der har ændret i mellemtiden (ligesom hvis de forespurgte den levende, konstant skiftende database).

Med alle disse antagelser i tankerne er her en illustrativ sekvens af begivenheder for de første 75 minutter af vores implementering:

tid begivenheder visuel
12:00 (t0)
  • Sikkerhedskopieringslog t0
  • Spark brugere ud af instans A
  • Gendan log t0 til instans A (STANDBY)
  • Nye skrivebeskyttede forespørgsler vil gå til instans A
12:15 (t1)
  • Sikkerhedskopieringslog t1
  • Spark brugere ud af instans B
  • Gendan log t0 til instans B (NORECOVERY)
  • Gendan log t1 til instans B (STANDBY)
  • Nye skrivebeskyttede forespørgsler vil gå til instans B
  • Eksisterende skrivebeskyttede forespørgsler til instans A kan fortsætte med at køre, men ca. 15 minutter efter
12:30 (t2)
  • Sikkerhedskopieringslog t2
  • Spark brugere ud af instans C
  • Gendan logfiler t0 -> t1 til instans C (NORECOVERY)
  • Gendan log t2 til instans C (STANDBY)
  • Nye skrivebeskyttede forespørgsler vil gå til instans C
  • Eksisterende skrivebeskyttede forespørgsler til instanser A og B kan fortsætte med at køre (15-30 minutter efter)
12:45 (t3)
  • Sikkerhedskopieringslog t3
  • Spark brugere ud af instans D
  • Gendan logfiler t0 -> t2 til instans D (NORECOVERY)
  • Gendan log t3 til instans D (STANDBY)
  • Nye skrivebeskyttede forespørgsler vil gå til instans D
  • Eksisterende skrivebeskyttede forespørgsler til instanserne A, B og C kan fortsætte med at køre (15-45 minutter efter)
13:00 (t4)
  • Sikkerhedskopieringslog t4
  • Spark brugere ud af instans A
  • Gendan logfiler t1 -> t3 til instans A (NORECOVERY)
  • Gendan log t4 til instans A (STANDBY)
  • Nye skrivebeskyttede forespørgsler vil gå til instans A
  • Eksisterende skrivebeskyttede forespørgsler til instans B, C og D kan fortsætte med at køre (15-45 minutter efter)
  • Forespørgsler, der stadig kører på instans A, siden t0 -> ~t1 (45-60 minutter) vil blive annulleret


Det kan virke simpelt nok; at skrive koden for at håndtere alt det er lidt mere skræmmende. En grov oversigt:

  1. På den primære server (jeg kalder den BOSS). ), oprette en database. Inden du overhovedet tænker på at gå videre, skal du aktivere Trace Flag 3226 for at forhindre vellykkede sikkerhedskopieringsmeddelelser i at henfalde SQL Servers fejllog.
  2. BOSS , tilføje en linket server for hver sekundær (jeg kalder dem PEON1 -> PEON4 ).
  3. Et sted, der er tilgængeligt for alle servere, skal du oprette en fildeling for at gemme sikkerhedskopier af databaser/logfiler og sikre, at tjenestekontiene for hver forekomst har læse-/skriveadgang. Desuden skal hver sekundær forekomst have en placering angivet for standby-filen.
  4. I en separat hjælpedatabase (eller MSDB, hvis du foretrækker det), skal du oprette tabeller, der indeholder konfigurationsoplysninger om databasen/databaserne, alle de sekundære, og log sikkerhedskopiering og gendannelseshistorik.
  5. Opret lagrede procedurer, der vil sikkerhedskopiere databasen og gendanne til de sekundære WITH NORECOVERY , og anvend derefter én log MED STANDBY , og marker én forekomst som den aktuelle standby sekundær. Disse procedurer kan også bruges til at geninitialisere hele logforsendelsesopsætningen i tilfælde af, at noget går galt.
  6. Opret et job, der kører hvert 15. minut, for at udføre de opgaver, der er beskrevet ovenfor:
    • sikkerhedskopier loggen
    • bestem, hvilken sekundær der skal anvendes eventuelle ikke-anvendte log-backups på
    • gendan disse logfiler med de relevante indstillinger
  7. Opret en lagret procedure (og/eller en visning?), der fortæller de kaldende applikationer, hvilken sekundær de skal bruge til nye skrivebeskyttede forespørgsler.
  8. Opret en oprydningsprocedure for at rydde logbackup-historikken for logfiler, der er blevet anvendt på alle sekundære (og måske også for at flytte eller rense selve filerne).
  9. Forøg løsningen med robust fejlhåndtering og meddelelser.

Trin 1 – opret en database

Min primære instans er Standard Edition, kaldet .\BOSS . I det tilfælde opretter jeg en simpel database med én tabel:

BRUG [master];GOCREATE DATABASE UserData;GOALTER DATABASE UserData SET RECOVERY FULL;GOUSE UserData;GOCREATE TABLE dbo.LastUpdate(EventTime DATETIME2);INSERT dbo.LastUpdate(EventTime) SELECT SYSDATETIME();

Derefter opretter jeg et SQL Server Agent-job, der blot opdaterer det tidsstempel hvert minut:

OPDATERING UserData.dbo.LastUpdate SET EventTime =SYSDATETIME();

Det opretter bare den indledende database og simulerer aktivitet, hvilket giver os mulighed for at validere, hvordan logforsendelsesopgaven roterer gennem hver af de læsbare sekundærer. Jeg vil udtrykkeligt sige, at pointen med denne øvelse ikke er at stressteste logforsendelse eller at bevise, hvor meget volumen vi kan slå igennem; det er en helt anden øvelse.

Trin 2 – tilføj linkede servere

Jeg har fire sekundære Express Edition-instanser med navnet .\PEON1 , .\PEON2 , .\PEON3 , og .\PEON4 . Så jeg kørte denne kode fire gange og ændrede @s hver gang:

BRUG [master];GODECLARE @s NVARCHAR(128) =N'.\PEON1', -- gentag for .\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 kompatibel', @optvalue =@t;EXEC [master].dbo.sp_serveroption @server =@s, @optname =N'dataadgang', @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;

Trin 3 – valider fildeling(er)

I mit tilfælde er alle 5 instanser på den samme server, så jeg har lige oprettet en mappe for hver instans:C:\temp\Peon1\ , C:\temp\Peon2\ , og så videre. Husk, at hvis dine sekundære servere er på forskellige servere, skal placeringen være i forhold til den pågældende server, men stadig være tilgængelig fra den primære (så typisk vil en UNC-sti blive brugt). Du bør validere, at hver instans kan skrive til den share, og du bør også validere, at hver instans kan skrive til den placering, der er angivet for standby-filen (jeg brugte de samme mapper til standby). Du kan validere dette ved at sikkerhedskopiere en lille database fra hver instans til hver af dens specificerede placeringer – fortsæt ikke, før dette virker.

Trin 4 – opret tabeller

Jeg besluttede at placere disse data i msdb , men jeg har ikke rigtig nogen stærke følelser for eller imod at oprette en separat database. Den første tabel, jeg skal bruge, er den, der indeholder oplysninger om de(n) database(r), jeg skal logforsende:

CREATE TABLE dbo.PMAG_Databases( DatabaseName SYSNAME, LogBackupFrequency_Minutes SMALLINT NOT NULL DEFAULT (15), CONSTRAINT PK_DBS PRIMÆR NØGLE(DatabaseName));GO INSERT dbo.PMAG_Databases(DatabaseN'UserDatabaseName;

(Hvis du er nysgerrig efter navneskemaet, står PMAG for "Poor Man's Availability Groups.")

En anden påkrævet tabel er en til at indeholde oplysninger om de sekundære, inklusive deres individuelle mapper og deres aktuelle status i logforsendelsessekvensen.

CREATE TABLE dbo.PMAG_Secondaries( DatabaseName SYSNAME, ServerInstance SYSNAME, CommonFolder VARCHAR(512) NOT NULL, DataFolder VARCHAR(512) NOT NULL, LogFolder VARCHAR(512) NOT NULL, StandBy NULL STANDARD 0, CONSTRAINT PK_Sec PRIMÆR NØGLE(DatabaseName, ServerInstance), CONSTRAINT FK_Sec_DBs UDENLANDSKE KEY(DatabaseName) REFERENCES dbo.PMAG_Databases(DatabaseName));

Hvis du vil sikkerhedskopiere fra kildeserveren lokalt og få sekundærerne til at gendanne eksternt, eller omvendt, kan du opdele CommonFolder i to kolonner (BackupFolder og Gendan mappe ), og lav relevante ændringer i koden (der vil ikke være så mange).

Da jeg kan udfylde denne tabel, i det mindste delvist baseret på oplysningerne i sys.servers – udnytter det faktum, at data/log og andre mapper er opkaldt efter instansnavnene:

INSERT dbo.PMAG_Secondaries( DatabaseName, ServerInstance, CommonFolder, DataFolder, LogFolder, StandByLocation)SELECT DatabaseName =N'UserData', ServerInstance =name, CommonFolder ='C:\temp\Peon' + HØJRE(navn, 1) + '\', DataFolder ='C:\Program Files\Microsoft SQL Server\MSSQL12.PEON' + HØJRE(navn, 1) + '\MSSQL\DATA\', LogFolder ='C:\Program Files\Microsoft SQL Server\ MSSQL12.PEON' + HØJRE(navn, 1) + '\MSSQL\DATA\', StandByLocation ='C:\temp\Peon' + HØJRE(navn, 1) + '\' FRA sys.servere WHERE navn LIKE N' .\PEON[1-4]';

Jeg har også brug for en tabel til at spore individuelle log backups (ikke kun den sidste), fordi jeg i mange tilfælde bliver nødt til at gendanne flere logfiler i en sekvens. Jeg kan få disse oplysninger fra msdb.dbo.backupset , men det er meget mere kompliceret at få ting som placeringen – og jeg har muligvis ikke kontrol over andre jobs, som kan rydde op i backuphistorikken.

OPRET TABEL dbo.PMAG_LogBackupHistory( DatabaseName SYSNAME, ServerInstance SYSNAME, BackupSetID INT NOT NULL, Location VARCHAR(2000) NOT NULL, BackupTime DATETIME NOT NULL DEFAULT SYSDATETIME(), CONSTRAINT PKMARY KEYHAIN,CONSTRAINT PK_LBEYH stance,CONSTRAINT PK_LBEYH FK_LBH_DBs UDENLANDSKE NØGLE(DatabaseName) REFERENCES dbo.PMAG_Databases(DatabaseName), CONSTRAINT FK_LBH_Sec UDENLANDSKE KEY(DatabaseName, ServerInstance) REFERENCES dbo.PMAG_Secondaries(DatabaseName, ServerInstance));

Du synes måske, det er spild at gemme en række for hver sekundær og at gemme placeringen af ​​hver sikkerhedskopi, men dette er for fremtidssikring – for at håndtere sagen, hvor du flytter CommonFolder for enhver sekundær.

Og endelig gendanner en historie med loggendannelser, så jeg på ethvert tidspunkt kan se, hvilke logfiler der er blevet gendannet og hvor, og gendannelsesjobbet kan være sikker på kun at gendanne logfiler, der ikke allerede er blevet gendannet:

OPRET TABEL dbo.PMAG_LogRestoreHistory( DatabaseName SYSNAME, ServerInstance SYSNAME, BackupSetID INT, RestoreTime DATETIME, CONSTRAINT PK_LRH PRIMÆR NØGLE(DatabaseName, ServerInstance, BackupSetID), dConSTRAINT FK_LRH_EYDBs FOREIFaCLRH_DBs(DatabaseName, ServerInstance, BackupSetID), CONSTRAINT FK_LRH_EYDBs. UDENLANDSKE NØGLE(DatabaseName, ServerInstance) REFERENCER dbo.PMAG_Secondaries(DatabaseName, ServerInstance));

Trin 5 – initialiser sekundærer

Vi har brug for en lagret procedure, der genererer en sikkerhedskopifil (og spejler den til enhver placering, der kræves af forskellige instanser), og vi vil også gendanne en log til hver sekundær for at sætte dem alle i standby. På dette tidspunkt vil de alle være tilgængelige for skrivebeskyttede forespørgsler, men kun én vil være den "aktuelle" standby ad gangen. Dette er den lagrede procedure, der vil håndtere både fuld backup og transaktionslog backup; når der anmodes om en fuld backup, og @init er indstillet til 1, geninitialiserer den automatisk logforsendelse.

OPRET PROCEDURE [dbo].[PMAG_Backup] @dbname SYSNAME, @type CHAR(3) ='bak', -- eller 'trn' @init BIT =0 -- bruges kun med 'bak'ASBEGIN SET NOCOUNT ON; -- generer et filnavnmønster DECLARE @now DATETIME =SYSDATETIME(); ERKLÆR @fn NVARCHAR(256) =@dbnavn + N'_' + KONVERTER(CHAR(8), @nu, 112) + HØJRE(REPLICATE('0',6) + KONVERTER(VARCHAR(32), DATODIFF(SECOND) , KONVERTER(DATO, @nu), @nu)), 6) + N'.' + @type; -- generer en backup-kommando med MIRROR TO for hver distinkt CommonFolder DECLARE @sql NVARCHAR(MAX) =N'BACKUP' + CASE @type WHEN 'bak' THEN N' DATABASE ' ELSE N' LOG ' END + QUOTENAME(@dbname) + ' ' + TING( (SELECT DISTINCT CHAR(13) + CHAR(10) + N' MIRROR TO DISK =''' + s.CommonFolder + @fn + '''' FRA dbo.PMAG_Secondaries AS s WHERE s.DatabaseName =@dbname FOR XML PATH(''), TYPE).value(N'.[1]',N'nvarchar(max)'),1,9,N'') + N' MED NAVN =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') SÅ N', KOMPRESSION;' ELSE N';' ENDE; EXEC [master].sys.sp_executesql @sql; HVIS @type ='bak' OG @init =1 -- initialiser logforsendelse BEGIN EXEC dbo.PMAG_InitializeSecondaries @dbname =@dbname, @fn =@fn; END IF @type ='trn' BEGIN -- registrer det faktum, at vi har sikkerhedskopieret en log INSERT dbo.PMAG_LogBackupHistory ( DatabaseName, ServerInstance, BackupSetID, Location ) SELECT DatabaseName =@dbname, ServerInstance =s.ServerInstance, BackupSetID =MAX(b .backup_set_id), Location =s.CommonFolder + @fn FRA msdb.dbo.backupset AS b CROSS JOIN dbo.PMAG_Secondaries AS s HVOR b.name =@dbname + N'_PMAGLog' OG s.DatabaseName [email protected] GRUPPE AF . ServerInstance, s.CommonFolder + @fn; -- når vi har sikkerhedskopieret logfiler, -- gendan dem på den næste sekundære EXEC dbo.PMAG_RestoreLogs @dbname =@dbname; SLUT

Dette kalder igen to procedurer, som du kunne kalde separat (men højst sandsynligt ikke vil). Først den procedure, der vil initialisere sekundærerne ved første kørsel:

ÆNDRINGSPROCEDURE dbo.PMAG_InitializeSecondaries @dbname SYSNAME, @fn VARCHAR(512)ASBEGIN INDSTIL NOCOUNT ON; -- Ryd eksisterende historik/indstillinger (da dette kan være en re-init) SLET dbo.PMAG_LogBackupHistory WHERE DatabaseName =@dbname; SLET dbo.PMAG_LogRestoreHistory WHERE DatabaseName =@dbname; OPDATERING dbo.PMAG_Secondaries SET IsCurrentStandby =0 WHERE DatabaseName =@dbname; DECLARE @sql NVARCHAR(MAX) =N'', @filer NVARCHAR(MAX) =N''; -- har brug for at kende de logiske filnavne - kan være mere end to SET @sql =N'SELECT @filer =(VÆLG N'', FLYT N''''''' + navn + '''''' TIL N ''''$'' + CASE [type] WHEN 0 THEN N''df'' WHEN 1 THEN N''lf'' END + ''$''''''' FRA ' + QUOTENAME(@dbname) + '.sys.database_files WHERE [indtast] IN (0,1) FOR XML PATH, TYPE).value(N''.[1]'',N''nvarchar(max)'');'; EXEC master.sys.sp_executesql @sql, N'@filer NVARCHAR(MAX) OUTPUT', @filer =@filer OUTPUT; SET @sql =N''; -- gendan - har brug for fysiske stier til data/logfiler til MED FLYT -- dette kan naturligvis mislykkes, hvis disse sti+navne allerede eksisterer for en anden db SELECT @sql +=N'EXEC ' + QUOTENAME(ServerInstance) + N' .master.sys.sp_executesql N''RESTORE DATABASE ' + QUOTENAME(@dbname) + N' FRA DISK =N''''' + CommonFolder + @fn + N''''''' + N' MED ERSTAT, NORGEVINDING ' + REPLACE(REPLACE(REPLACE(@filer, N'$df$', DataFolder + @dbname + N'.mdf'), N'$lf$', LogFolder + @dbname + N'.ldf'), N '''', N'''''') + N';'';' + CHAR(13) + CHAR(10) FRA dbo.PMAG_Secondaries WHERE DatabaseName =@dbname; EXEC [master].sys.sp_executesql @sql; -- sikkerhedskopiere en log for denne database EXEC dbo.PMAG_Backup @dbname =@dbname, @type ='trn'; -- gendan logfiler EXEC dbo.PMAG_RestoreLogs @dbname =@dbname, @PrepareAll =1;END

Og derefter proceduren, der vil gendanne logfilerne:

OPRET PROCEDURE dbo.PMAG_RestoreLogs @dbname SYSNAME, @PrepareAll BIT =0AS BEGIN SET NOCOUNT ON; DECLARE @StandbyInstance SYSNAME, @CurrentInstance SYSNAME, @BackupSetID INT, @Location VARCHAR(512), @StandByLocation VARCHAR(512), @sql NVARCHAR(MAX), @rn INT; -- få den "næste" standby-instans SELECT @StandbyInstance =MIN(ServerInstance) FRA dbo.PMAG_Secondaries WHERE IsCurrentStandby =0 OG ServerInstance> (SELECT ServerInstance FRA dbo.PMAG_Secondaries WHERE IsCurrentStandBy =1); HVIS @StandbyInstance ER NULL -- enten var det sidst eller en re-init. BEGIN SELECT @StandbyInstance =MIN(ServerInstance) FRA dbo.PMAG_Secondaries; END -- få den instans op og ind i STANDBY -- for hver log ind logbackuphistory ikke i logrestorehistory:-- gendan, og indsæt den i logrestorehistory -- marker den sidste som STANDBY -- hvis @prepareAll er sand, marker alle andre som NORECOVERY -- i dette tilfælde bør der kun være én, men bare i tilfælde af DECLAR c CURSOR LOCAL FAST_FORWARD FOR SELECT bh.BackupSetID, s.ServerInstance, bh.Location, s.StandbyLocation, rn =ROW_NUMBER() OVER (PARTITION BY s. ServerInstance BESTILLE AF bh.BackupSetID DESC) FRA dbo.PMAG_LogBackupHistory AS bh INNER JOIN dbo.PMAG_Secondaries AS s ON bh.DatabaseName =s.DatabaseName OG bh.ServerInstance =s.ServerInstance.DatadSInstance WHEREBasInstance WHEREInstance. @PrepareAll WHEN 1 THEN s.ServerInstance ELSE @StandbyInstance END AND NOT EXISTS (VÆLG 1 FRA dbo.PMAG_LogRestoreHistory AS rh WHERE DatabaseName =@dbname AND ServerInstance =s.ServerInstance AND BackupBetID =bh. ackupSetID ) ORDER BY CASE s.ServerInstance WHEN @StandbyInstance THEN 1 ELSE 2 END, bh.BackupSetID; ÅBEN c; FETCH c INTO @BackupSetID, @CurrentInstance, @Location, @StandbyLocation, @rn; WHILE @@FETCH_STATUS -1 BEGIN -- spark brugere ud - sæt til single_user og derefter tilbage til multi SET @sql =N'EXEC ' + QUOTENAME(@CurrentInstance) + N'.[master].sys.sp_executesql ' + 'N' 'HVIS FINDER (VÆLG 1 FRA sys.databases WHERE name =N''''' + @dbname + ''''' OG [state] 1) START ÆNDRING DATABASE ' + QUOTENAME(@dbname) + N' SET SINGLE_USER ' + N'MED TILBAGE TILBAGE STRAKS; ALTER DATABASE ' + QUOTENAME(@dbname) + N' SET MULTI_USER; ENDE;'';'; EXEC [master].sys.sp_executesql @sql; -- gendan loggen (i STANDBY, hvis det er den sidste):SET @sql =N'EXEC ' + QUOTENAME(@CurrentInstance) + N'.[master].sys.sp_executesql ' + N'N''RESTORE LOG ' + QUOTENAME(@dbname) + N' FRA DISK =N''''' + @Location + N''''' MED ' + CASE NÅR @rn =1 OG (@CurrentInstance =@StandbyInstance ELLER @PrepareAll =1) THEN N'STANDBY =N''''' + @StandbyLocation + @dbname + N'.standby''''' ELSE N'NORECOVERY' END + N';'';'; EXEC [master].sys.sp_executesql @sql; -- registrere det faktum, at vi har gendannet logfiler. INSERT dbo.PMAG_LogRestoreHistory (DatabaseName, ServerInstance, BackupSetID, RestoreTime) SELECT @dbname, @CurrentInstance, @BackupSetID, SYSDATETIME(); -- marker den nye standby HVIS @rn =1 OG @CurrentInstance =@StandbyInstance -- dette er den nye STANDBY BEGIN UPDATE dbo.PMAG_Secondaries SET IsCurrentStandby =CASE ServerInstance WHEN @StandbyInstance THEN 1 ELSE 0 END WHERE DatabaseName =@dbname; AFSLUT FETCH c INTO @BackupSetID, @CurrentInstance, @Location, @StandbyLocation, @rn; SLUT LUK c; DEALLOCATE c;END

(Jeg ved, det er en masse kode og en masse kryptisk dynamisk SQL. Jeg prøvede at være meget liberal med kommentarer; hvis der er et stykke, du har problemer med, så lad mig det vide.)

Så nu, alt du skal gøre for at få systemet op at køre er at lave to procedurekald:

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

Nu skulle du se hver instans med en standby-kopi af databasen:

Og du kan se, hvilken der i øjeblikket skal fungere som skrivebeskyttet standby:

SELECT ServerInstance, IsCurrentStandby FROM dbo.PMAG_Secondaries WHERE DatabaseName =N'UserData';

Trin 6 – opret et job, der sikkerhedskopierer / gendanner logfiler

Du kan indsætte denne kommando i et job, du planlægger for hvert 15. minut:

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

Dette vil flytte den aktive sekundær hvert 15. minut, og dens data vil være 15 minutter friskere end den tidligere aktive sekundær. Hvis du har flere databaser på forskellige tidsplaner, kan du oprette flere job eller planlægge jobbet oftere og tjekke dbo.PMAG_Databases tabel for hver enkelt LogBackupFrequency_Minutes værdi for at bestemme, om du skal køre backup/gendannelse for den pågældende database.

Trin 7 – visning og procedure for at fortælle applikationen, hvilken standby der er aktiv

OPRET VISNING dbo.PMAG_ActiveSecondariesAS VÆLG DatabaseName, ServerInstance FRA dbo.PMAG_Secondaries WHERE IsCurrentStandby =1;GÅ OPRET PROCEDURE dbo.PMAG_GetActiveSecondary @dbname SYSNAMEASBEGIN SÆT ANTAL TIL; VÆLG ServerInstance FRA dbo.PMAG_ActiveSecondaries WHERE DatabaseName =@dbname;ENDGO

I mit tilfælde oprettede jeg også manuelt en visningsforening på tværs af alle UserData databaser, så jeg kunne sammenligne fornyelsen af ​​dataene på den primære med hver sekundær.

OPRET VISNING dbo.PMAG_CompareRecency_UserDataAS MED x(ServerInstance, EventTime) AS (VÆLG @@SERVERNAME, EventTime FRA UserData.dbo.LastUpdate UNION ALLE VÆLG N'.\PEON1', EventTime FRA [.\PEON1].dboData .LastUpdate UNION ALLE VÆLG N'.\PEON2', EventTime FRA [.\PEON2].UserData.dbo.LastUpdate UNION ALLE VÆLG N'.\PEON3', EventTime FRA [.\PEON3].UserData.dbo.LastUpdate UNION ALLE VÆLG N'.\PEON4', EventTime FRA [.\PEON4].UserData.dbo.LastUpdate ) VÆLG x.ServerInstance, s.IsCurrentStandby, x.EventTime, Age_Minutes =DATEDIFF(MINUTE, x.EventTime, SYSDATETIME()), Age_Seconds =DATEDIFF(SECOND, x.EventTime, SYSDATETIME()) FROM x VENSTRE YDRE JOIN dbo.PMAG_Secondaries AS s ON s.ServerInstance =x.ServerInstance OG s.DatabaseName =N'UserData';GO

Eksempelresultater fra weekenden:

VÆLG [Nu] =SYSDATETIME(); SELECT ServerInstance, IsCurrentStandby, EventTime, Age_Minutes, Age_Seconds FROM dbo.PMAG_CompareRecency_UserData ORDER BY Age_Seconds DESC;

Trin 8 – oprydningsprocedure

Det er ret nemt at rydde op i log backup og gendannelseshistorik.

OPRET PROCEDURE dbo.PMAG_CleanupHistory @dbname SYSNAME, @DaysOld INT =7AS BEGIN INDSTILL INGEN ANTAL TIL; DECLARE @cutoff INT; -- dette forudsætter, at en log backup enten -- lykkedes eller mislykkedes på alle sekundære SELECT @cutoff =MAX(BackupSetID) FROM dbo.PMAG_LogBackupHistory AS bh WHERE DatabaseName =@dbname AND BackupTime  

Nu kan du tilføje det som et trin i det eksisterende job, eller du kan planlægge det helt separat eller som en del af andre oprydningsrutiner.

Jeg vil lade rydde op i filsystemet til et andet indlæg (og sandsynligvis en separat mekanisme helt, såsom PowerShell eller C# – det er typisk ikke den slags ting, du vil have T-SQL til at gøre).

Trin 9 – øg løsningen

Det er rigtigt, at der kunne være bedre fejlhåndtering og andre finesser her for at gøre denne løsning mere komplet. Indtil videre vil jeg lade det være en øvelse for læseren, men jeg planlægger at se på opfølgende indlæg for at detaljere forbedringer og justeringer af denne løsning.

Variabler og begrænsninger

Bemærk, at i mit tilfælde brugte jeg Standard Edition som den primære og Express Edition for alle sekundære. Du kan gå et skridt videre på budgetskalaen og endda bruge Express Edition som det primære – mange mennesker tror, ​​at Express Edition ikke understøtter logforsendelse, når det faktisk kun er guiden, der ikke var til stede i versioner af Management Studio Express før SQL Server 2012 Service Pack 1. Når det er sagt, da Express Edition ikke understøtter SQL Server Agent, ville det være svært at gøre det til en udgiver i dette scenarie – du skulle konfigurere din egen planlægger til at kalde de lagrede procedurer (C#) kommandolinje-app, der køres af Windows Task Scheduler, PowerShell-job eller SQL Server Agent-job på endnu en instans). For at bruge Express i begge ender skal du også være sikker på, at din datafil ikke overstiger 10 GB, og dine forespørgsler vil fungere fint med hukommelses-, CPU- og funktionsbegrænsningerne i den udgave. Jeg antyder på ingen måde, at Express er ideelt; Jeg brugte det blot til at demonstrere, at det er muligt at have meget fleksible læsbare sekundære gratis (eller meget tæt på det).

Også disse separate instanser i mit scenarie lever alle på den samme VM, men det behøver slet ikke at fungere på den måde – du kan sprede instanserne ud på flere servere; eller du kan gå den anden vej og gendanne til forskellige kopier af databasen, med forskellige navne, på den samme instans. Disse konfigurationer ville kræve minimale ændringer i forhold til det, jeg har beskrevet ovenfor. Og hvor mange databaser du gendanner til, og hvor ofte, er helt op til dig – selvom der vil være en praktisk øvre grænse (hvor [gennemsnitlig forespørgselstid]> [antal sekundære] x [log backup interval] ).

Endelig er der helt sikkert nogle begrænsninger med denne tilgang. En ikke-udtømmende liste:

  1. Mens du kan fortsætte med at tage fuld sikkerhedskopiering efter din egen tidsplan, skal log backups fungere som din eneste log backup mekanisme. Hvis du har brug for at gemme log-backuperne til andre formål, vil du ikke kunne sikkerhedskopiere logs separat fra denne løsning, da de vil forstyrre log-kæden. I stedet kan du overveje at tilføje yderligere MIRROR TO arguments to the existing log backup scripts, if you need to have copies of the logs used elsewhere.
  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. MySQL - hvor mange rækker kan jeg indsætte i en enkelt INSERT-sætning?

  2. Sådan returneres et inkrementelt gruppenummer pr. gruppe i SQL

  3. SQL:Vælg Top 3 Records + Sum of Quantity

  4. SCD Type 4