Denne måneds T-SQL-tirsdag afholdes af Mike Fal (blog | twitter), og emnet er Trick Shots, hvor vi er inviteret til at fortælle fællesskabet om en løsning, vi brugte i SQL Server, som føltes, i det mindste for os, som en slags "trick shot" – noget der ligner at bruge massé, "engelsk" eller komplicerede bankshots i billard eller snooker. Efter at have arbejdet med SQL Server i omkring 15 år, har jeg haft lejlighed til at komme med tricks til at løse nogle ret interessante problemer, men en, der ser ud til at være ret genbrugelig, let tilpasser sig mange situationer og er enkel at implementere, er noget jeg kalder "schema switch-a-roo."
Lad os sige, at du har et scenarie, hvor du har en stor opslagstabel, der skal opdateres med jævne mellemrum. Denne opslagstabel er nødvendig på tværs af mange servere og kan indeholde data, der bliver udfyldt fra en ekstern eller 3. parts kilde, f.eks. IP- eller domænedata, eller kan repræsentere data fra dit eget miljø.
De første par scenarier, hvor jeg havde brug for en løsning til dette, var at gøre metadata og denormaliserede data tilgængelige for skrivebeskyttede "datacaches" - egentlig bare SQL Server MSDE (og senere Express)-instanser installeret på forskellige webservere, så webserverne trak denne cachelagrede data lokalt i stedet for at genere det primære OLTP-system. Dette kan virke overflødigt, men aflastning af læseaktivitet væk fra det primære OLTP-system og at være i stand til at tage netværksforbindelsen ud af ligningen fuldstændigt førte til et reelt bump i den generelle ydeevne og især for slutbrugere .
Disse servere havde ikke brug for ajourførte kopier af dataene; faktisk blev mange af cachetabellerne kun opdateret dagligt. Men da systemerne var 24×7, og nogle af disse opdateringer kunne tage flere minutter, kom de ofte i vejen for rigtige kunder, der gjorde rigtige ting på systemet.
Den oprindelige tilgang(er)
I begyndelsen var koden ret forenklet:vi slettede rækker, der var blevet fjernet fra kilden, opdaterede alle de rækker, som vi kunne se var ændret, og indsatte alle de nye rækker. Det så nogenlunde sådan her ud (fejlhåndtering osv. fjernet for kortheds skyld):
BEGIN TRANSACTION; DELETE dbo.Lookup WHERE [key] NOT IN (SELECT [key] FROM [source]); UPDATE d SET [col] = s.[col] FROM dbo.Lookup AS d INNER JOIN [source] AS s ON d.[key] = s.[key] -- AND [condition to detect change]; INSERT dbo.Lookup([cols]) SELECT [cols] FROM [source] WHERE [key] NOT IN (SELECT [key] FROM dbo.Lookup); COMMIT TRANSACTION;
Det er overflødigt at sige, at denne transaktion kunne forårsage nogle reelle problemer med ydeevnen, når systemet var i brug. Der var helt sikkert andre måder at gøre dette på, men hver metode, vi prøvede, var lige langsom og dyr. Hvor langsomt og dyrt? "Lad mig tælle scanningerne..."
Siden denne før-daterede MERGE, og vi allerede havde kasseret "eksterne" tilgange som DTS, fandt vi gennem nogle tests ud af, at det ville være mere effektivt blot at slette tabellen og genudfylde den i stedet for at prøve at synkronisere til kilden :
BEGIN TRANSACTION; TRUNCATE TABLE dbo.Lookup; INSERT dbo.Lookup([cols]) SELECT [cols] FROM [source]; COMMIT TRANSACTION;
Nu, som jeg forklarede, kunne denne forespørgsel fra [kilde] tage et par minutter, især hvis alle webserverne blev opdateret parallelt (vi forsøgte at vakle, hvor vi kunne). Og hvis en kunde var på webstedet og prøvede at køre en forespørgsel, der involverede opslagstabellen, måtte de vente på, at transaktionen blev afsluttet. I de fleste tilfælde, hvis de kører denne forespørgsel ved midnat, ville det ikke være lige meget, om de fik gårsdagens kopi af opslagsdataene eller dagens; så det virkede fjollet at få dem til at vente på opdateringen, og det førte faktisk til en række supportopkald.
Så selvom dette var bedre, var det bestemt langt fra perfekt.
Min oprindelige løsning:sp_rename
Min første løsning, dengang SQL Server 2000 var cool, var at oprette en "skygge"-tabel:
CREATE TABLE dbo.Lookup_Shadow([cols]);
På denne måde kunne jeg udfylde skyggetabellen uden at afbryde brugere overhovedet, og derefter udføre en tre-vejs omdøbning – en hurtig, kun metadata-operation – først efter at populationen var fuldført. Noget som dette (igen, groft forenklet):
TRUNCATE TABLE dbo.Lookup_Shadow; INSERT dbo.Lookup_Shadow([cols]) SELECT [cols] FROM [source]; BEGIN TRANSACTION; EXEC sp_rename N'dbo.Lookup', N'dbo.Lookup_Fake'; EXEC sp_rename N'dbo.Lookup_Shadow', N'dbo.Lookup'; COMMIT TRANSACTION; -- if successful: EXEC sp_rename N'dbo.Lookup_Fake', N'dbo.Lookup_Shadow';
Ulempen ved denne indledende tilgang var, at sp_rename har en ikke-undertrykkelig outputmeddelelse, der advarer dig om farerne ved at omdøbe objekter. I vores tilfælde udførte vi denne opgave gennem SQL Server Agent-job, og vi håndterede en masse metadata og andre cache-tabeller, så jobhistorikken blev oversvømmet med alle disse ubrugelige meddelelser og faktisk forårsagede, at rigtige fejl blev afkortet fra historiedetaljerne. (Jeg klagede over dette i 2007, men mit forslag blev i sidste ende afvist og lukket som "Vil ikke rette")
En bedre løsning:Skemaer
Da vi opgraderede til SQL Server 2005, opdagede jeg denne fantastiske kommando kaldet CREATE SCHEMA. Det var trivielt at implementere den samme type løsning ved hjælp af skemaer i stedet for at omdøbe tabeller, og nu ville agenthistorien ikke blive forurenet med alle disse uhensigtsmæssige beskeder. Grundlæggende oprettede jeg to nye skemaer:
CREATE SCHEMA fake AUTHORIZATION dbo; CREATE SCHEMA shadow AUTHORIZATION dbo;
Så flyttede jeg Lookup_Shadow-tabellen ind i cache-skemaet og omdøbte den:
ALTER SCHEMA shadow TRANSFER dbo.Lookup_Shadow; EXEC sp_rename N'shadow.Lookup_Shadow', N'Lookup';
(Hvis du bare implementerer denne løsning, vil du oprette en ny kopi af tabellen i skemaet, ikke flytte den eksisterende tabel dertil og omdøbe den.)
Med disse to skemaer på plads og en kopi af opslagstabellen i skyggeskemaet, blev mit tre-vejs omdøb til en tre-vejs skemaoverførsel:
TRUNCATE TABLE shadow.Lookup; INSERT shadow.Lookup([cols]) SELECT [cols] FROM [source]; -- perhaps an explicit statistics update here BEGIN TRANSACTION; ALTER SCHEMA fake TRANSFER dbo.Lookup; ALTER SCHEMA dbo TRANSFER shadow.Lookup; COMMIT TRANSACTION; ALTER SCHEMA shadow TRANSFER fake.Lookup;
På dette tidspunkt kan du selvfølgelig tømme skyggekopien af tabellen, men i nogle tilfælde fandt jeg det nyttigt at efterlade den "gamle" kopi af dataene til fejlfindingsformål:
TRUNCATE TABLE shadow.Lookup;
Alt andet, du gør med skyggekopien, skal du sørge for at gøre uden for transaktionen – de to overførselshandlinger skal være så kortfattede og hurtige som muligt.
Nogle forbehold
- Udenlandske nøgler
Dette vil ikke fungere direkte, hvis opslagstabellen refereres af fremmednøgler. I vores tilfælde pegede vi ikke på nogen begrænsninger på disse cache-tabeller, men hvis du gør det, kan du blive nødt til at holde dig til påtrængende metoder såsom MERGE. Eller brug kun tilføjelsesmetoder og deaktiver eller slip fremmednøglerne, før du udfører nogen dataændringer (så genskab eller genaktiver dem bagefter). Hvis du holder dig til MERGE / UPSERT-teknikker, og du gør dette mellem servere eller, værre endnu, fra et fjernsystem, anbefaler jeg stærkt at hente de rå data lokalt i stedet for at prøve at bruge disse metoder mellem servere.
- Statistik
Skiftning af tabeller (ved at bruge omdøbning eller skemaoverførsel) vil føre til, at statistik bladrer frem og tilbage mellem de to kopier af tabellen, og dette kan naturligvis være et problem for planer. Så du kan overveje at tilføje eksplicitte statistikopdateringer som en del af denne proces.
- Andre tilgange
Der er selvfølgelig andre måder at gøre dette på, som jeg simpelthen ikke har haft lejlighed til at prøve. Skift af partition og brug af en visning + synonym er to tilgange, jeg kan undersøge i fremtiden for en mere grundig behandling af emnet. Jeg ville være interesseret i at høre dine erfaringer og hvordan du har løst dette problem i dit miljø. Og ja, jeg er klar over, at dette problem i høj grad løses af Availability Groups og læsbare sekundære i SQL Server 2012, men jeg betragter det som et "trick shot", hvis du kan løse problemet uden at smide avancerede licenser efter problemet, eller at replikere en hele databasen for at gøre nogle få tabeller overflødige. :-)
Konklusion
Hvis du kan leve med begrænsningerne her, kan denne tilgang meget vel være en bedre performer end et scenario, hvor du i det væsentlige tager et bord offline ved hjælp af SSIS eller din egen MERGE / UPSERT-rutine, men sørg for at teste begge teknikker. Det vigtigste punkt er, at slutbrugeren, der får adgang til tabellen, skal have nøjagtig den samme oplevelse, når som helst på dagen, selvom de rammer bordet midt i din periodiske opdatering.