De T-SQL-dinsdag van deze maand wordt gehost door Mike Fal (blog | twitter), en het onderwerp is Trick Shots, waar we worden uitgenodigd om de gemeenschap te vertellen over een oplossing die we in SQL Server hebben gebruikt die, althans voor ons, als een soort "trickshot" - iets wat lijkt op het gebruik van massé, "English" of gecompliceerde bankshots in biljart of snooker. Na zo'n 15 jaar met SQL Server te hebben gewerkt, heb ik de gelegenheid gehad om trucs te bedenken om een aantal behoorlijk interessante problemen op te lossen, maar een die redelijk herbruikbaar lijkt, zich gemakkelijk aan veel situaties aanpast en eenvoudig te implementeren is, is iets wat ik 'schema switch-a-roo' noem.
Stel dat u een scenario heeft waarin u een grote opzoektabel hebt die periodiek moet worden vernieuwd. Deze opzoektabel is nodig voor veel servers en kan gegevens bevatten die worden ingevuld door een externe of externe bron, bijv. IP- of domeingegevens, of kunnen gegevens uit uw eigen omgeving vertegenwoordigen.
De eerste paar scenario's waar ik een oplossing voor nodig had, waren het beschikbaar stellen van metadata en gedenormaliseerde data voor alleen-lezen "datacaches" - eigenlijk alleen SQL Server MSDE (en later Express) instances geïnstalleerd op verschillende webservers, dus de webservers trokken deze gegevens worden lokaal in de cache opgeslagen in plaats van het primaire OLTP-systeem lastig te vallen. Dit lijkt misschien overbodig, maar het ontlasten van leesactiviteit weg van het primaire OLTP-systeem en het volledig uit de vergelijking halen van de netwerkverbinding leidde tot een echte hobbel in de algehele prestaties en, met name, voor eindgebruikers .
Deze servers hadden geen up-to-date kopieën van de gegevens nodig; in feite werden veel van de cachetabellen alleen dagelijks bijgewerkt. Maar aangezien de systemen 24×7 waren en sommige van deze updates enkele minuten konden duren, stonden ze echte klanten vaak in de weg om echte dingen op het systeem te doen.
De oorspronkelijke benadering(en)
Helemaal in het begin was de code nogal simplistisch:we verwijderden rijen die uit de bron waren verwijderd, werkten alle rijen bij waarvan we konden zien dat ze waren veranderd en voegden alle nieuwe rijen in. Het zag er ongeveer zo uit (foutafhandeling etc. verwijderd voor de beknoptheid):
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;
Onnodig te zeggen dat deze transactie echte prestatieproblemen kan veroorzaken wanneer het systeem in gebruik was. Er waren vast andere manieren om dit te doen, maar elke methode die we probeerden was even traag en duur. Hoe traag en duur? "Laat me de scans tellen..."
Aangezien dit van vóór MERGE was, en we "externe" benaderingen zoals DTS al hadden weggegooid, hebben we door middel van enkele tests vastgesteld dat het efficiënter zou zijn om gewoon de tabel te wissen en opnieuw te vullen, in plaats van te proberen te synchroniseren met de bron :
BEGIN TRANSACTION; TRUNCATE TABLE dbo.Lookup; INSERT dbo.Lookup([cols]) SELECT [cols] FROM [source]; COMMIT TRANSACTION;
Zoals ik heb uitgelegd, kan deze vraag van [bron] een paar minuten duren, vooral als alle webservers parallel worden bijgewerkt (we probeerden te spreiden waar we konden). En als een klant op de site was en een zoekopdracht probeerde uit te voeren met betrekking tot de opzoektabel, moesten ze wachten tot die transactie was voltooid. In de meeste gevallen, als ze deze query om middernacht uitvoeren, maakt het niet echt uit of ze de kopie van de opzoekgegevens van gisteren of die van vandaag hebben; dus hen laten wachten op de verversing leek dwaas en leidde in feite tot een aantal ondersteuningsoproepen.
Dus hoewel dit beter was, was het zeker verre van perfect.
Mijn eerste oplossing:sp_rename
Mijn eerste oplossing, toen SQL Server 2000 nog cool was, was om een "schaduw"-tabel te maken:
CREATE TABLE dbo.Lookup_Shadow([cols]);
Op deze manier kon ik de schaduwtabel vullen zonder gebruikers te onderbreken, en vervolgens een drievoudige hernoeming uitvoeren - een snelle bewerking met alleen metagegevens - pas nadat de populatie voltooid was. Zoiets (nogmaals sterk vereenvoudigd):
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';
Het nadeel van deze aanvankelijke benadering was dat sp_rename een niet-onderdrukbaar uitvoerbericht heeft dat je waarschuwt voor de gevaren van het hernoemen van objecten. In ons geval voerden we deze taak uit via SQL Server Agent-taken, en we behandelden veel metadata en andere cachetabellen, dus de taakgeschiedenis werd overspoeld met al deze nutteloze berichten en zorgde er feitelijk voor dat echte fouten werden afgekapt uit de geschiedenisdetails. (Ik heb hier in 2007 over geklaagd, maar mijn suggestie werd uiteindelijk afgewezen en gesloten als "zal niet worden opgelost".)
Een betere oplossing:schema's
Toen we eenmaal een upgrade naar SQL Server 2005 hadden uitgevoerd, ontdekte ik deze fantastische opdracht met de naam CREATE SCHEMA. Het was triviaal om hetzelfde type oplossing te implementeren met behulp van schema's in plaats van tabellen te hernoemen, en nu zou de geschiedenis van de agent niet vervuild zijn met al deze nutteloze berichten. Eigenlijk heb ik twee nieuwe schema's gemaakt:
CREATE SCHEMA fake AUTHORIZATION dbo; CREATE SCHEMA shadow AUTHORIZATION dbo;
Daarna heb ik de Lookup_Shadow-tabel naar het cacheschema verplaatst en deze hernoemd:
ALTER SCHEMA shadow TRANSFER dbo.Lookup_Shadow; EXEC sp_rename N'shadow.Lookup_Shadow', N'Lookup';
(Als u deze oplossing alleen implementeert, maakt u een nieuwe kopie van de tabel in het schema, zonder de bestaande tabel daarheen te verplaatsen en de naam ervan te wijzigen.)
Met die twee schema's en een kopie van de opzoektabel in het schaduwschema, werd mijn hernoemen in drie richtingen een drieweg-schemaoverdracht:
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;
Op dit punt kunt u natuurlijk de schaduwkopie van de tabel leegmaken, maar in sommige gevallen vond ik het handig om de "oude" kopie van de gegevens rond te laten voor het oplossen van problemen:
TRUNCATE TABLE shadow.Lookup;
Alles wat u verder met de schaduwkopie doet, moet u zeker weten buiten de transactie doen - de twee overdrachtshandelingen moeten zo beknopt en snel mogelijk zijn.
Enkele waarschuwingen
- Buitenlandse sleutels
Dit werkt niet standaard als er naar de opzoektabel wordt verwezen met externe sleutels. In ons geval hebben we geen beperkingen op deze cachetabellen gewezen, maar als je dat wel doet, moet je je misschien houden aan opdringerige methoden zoals MERGE. Of gebruik alleen append-methoden en schakel de externe sleutels uit of verwijder ze voordat u gegevenswijzigingen uitvoert (maak ze daarna opnieuw of schakel ze opnieuw in). Als je bij MERGE / UPSERT-technieken blijft en je doet dit tussen servers of, erger nog, vanaf een systeem op afstand, raad ik je ten zeerste aan om de onbewerkte gegevens lokaal te krijgen in plaats van deze methoden tussen servers te proberen.
- Statistieken
Het wisselen van tabellen (met behulp van hernoemen of schemaoverdracht) zal ertoe leiden dat statistieken heen en weer gaan tussen de twee exemplaren van de tabel, en dit kan natuurlijk een probleem zijn voor plannen. U kunt dus overwegen om expliciete statistische updates toe te voegen als onderdeel van dit proces.
- Andere benaderingen
Er zijn natuurlijk andere manieren om dit te doen waar ik gewoon niet de gelegenheid voor heb gehad. Het wisselen van partities en het gebruik van een weergave + synoniem zijn twee benaderingen die ik in de toekomst kan onderzoeken voor een meer grondige behandeling van het onderwerp. Ik ben benieuwd naar je ervaringen en hoe je dit probleem in je omgeving hebt opgelost. En ja, ik realiseer me dat dit probleem grotendeels wordt opgelost door Beschikbaarheidsgroepen en leesbare secondaries in SQL Server 2012, maar ik beschouw het als een "trucje" als je het probleem kunt oplossen zonder high-end licenties op het probleem te gooien, of een replicatie van een hele database om een paar tabellen overbodig te maken. :-)
Conclusie
Als je met de beperkingen hier kunt leven, is deze aanpak misschien een betere prestatie dan een scenario waarin je in wezen een tafel offline haalt met SSIS of je eigen MERGE / UPSERT-routine, maar zorg ervoor dat je beide technieken test. Het belangrijkste punt is dat de eindgebruiker die toegang heeft tot de tafel, exact dezelfde ervaring moet hebben, op elk moment van de dag, zelfs als ze de tafel raken in het midden van je periodieke update.