Ett vanligt krav i ETL och olika rapporteringsscenarier är att tyst ladda en SQL Server-staging-tabell i bakgrunden, så att användare som frågar efter data inte påverkas av skrivningarna och vice versa. Tricket är hur och när du pekar användare på den nya, uppdaterade versionen av data.
Förenklat exempel på en iscensättningstabell:A Farmer's Market Analogy
Så, vad är en mellanställningstabell i SQL? Ett mellanställningsbord kan lättare förstås med ett exempel från verkligheten:Låt oss säga att du har ett bord fullt med grönsaker som du säljer på den lokala bondens marknad. När dina grönsaker säljs och du tar in nytt lager:
- När du tar med dig en mängd nya grönsaker kommer det att ta dig 20 minuter att rensa från bordet och byta ut resterande lager med den nyare produkten.
- Du vill inte att kunderna ska sitta där och vänta i 20 minuter på att bytet ska ske, eftersom de flesta kommer att få sina grönsaker någon annanstans.
Nu, tänk om du hade ett andra tomt bord där du laddar de nya grönsakerna, och medan du gör det kan kunderna fortfarande köpa de äldre grönsakerna från det första bordet? (Låt oss låtsas att det inte beror på att de äldre grönsakerna blev dåliga eller på annat sätt är mindre önskvärda.)
Uppdatera tabeller i SQL Server
Det finns flera metoder för att ladda om hela tabeller medan de aktivt frågas; för två decennier sedan tog jag otyglad fördel av sp_rename
— Jag skulle spela ett skalspel med en tom skuggkopia av bordet, glatt ladda om skuggkopian och sedan bara byta namn i en transaktion.
I SQL Server 2005 började jag använda scheman för att hålla skuggkopior av tabeller som jag helt enkelt överförde med samma skalspelsteknik, som jag skrev om i dessa två inlägg:
- Trickbilder:Schema Switch-a-Roo
- Schema Switch-a-Roo, del 2
Den enda fördelen med att överföra objekt mellan scheman framför att döpa om dem är att det inte finns några varningsmeddelanden om att byta namn på ett objekt – vilket inte ens är ett problem i sig, förutom att varningsmeddelandena fyller upp agenthistorikloggarna så mycket snabbare.
Båda tillvägagångssätten kräver fortfarande ett schemamodifieringslås (Sch-M), så de måste vänta på att befintliga transaktioner släpper sina egna lås. När de väl har fått sitt Sch-M-lås blockerar de alla efterföljande frågor som kräver schemastabilitetslås (Sch-S)... vilket är nästan varje fråga. Det kan snabbt bli en blockerande kedjas mardröm, eftersom alla nya frågor som behöver Sch-S måste hamna i en kö bakom Sch-M. (Och nej, du kan inte komma runt detta genom att använda RCSI eller NOLOCK
överallt, eftersom även dessa frågor fortfarande kräver Sch-S. Du kan inte förvärva Sch-S med en Sch-M på plats, eftersom de är inkompatibla – Michael J. Swart pratar om det här.)
Kendra Little öppnade verkligen mina ögon om farorna med schemaöverföring i sitt inlägg, "Staging Data:Locking Danger with ALTER SCHEMA TRANSFER." Där visar hon varför schemaöverföring kan vara värre än att byta namn. Hon beskrev senare ett tredje och mycket mindre effektfullt sätt att byta ut tabeller, som jag nu enbart använder:partitionsbyte. Den här metoden gör att växeln kan vänta med en lägre prioritet, vilket inte ens är ett alternativ med teknikerna för att byta namn eller schemaöverföring. Joe Sack gick in i detalj om den här förbättringen som lagts till i SQL Server 2014:"Utforskar alternativ för lågprioriterade låsväntetider i SQL Server 2014 CTP1."
Exempel på byte av SQL-serverpartition
Låt oss titta på ett grundläggande exempel, efter Kendras grundliga sammanfattning här. Först skapar vi två nya databaser:
CREATE DATABASE NewWay;CREATE DATABASE OldWay;GO
I den nya databasen skapar vi en tabell för vår grönsaksinventering och två kopior av tabellen för vårt skalspel:
ANVÄND NewWay;GO CREATE TABLE dbo.Vegetables_NewWay( VegetableID int, Name sysname, WhenPicked datetime, BackStory nvarchar(max));GO -- vi måste skapa två extra kopior av tabellen. CREATE TABLE dbo.Vegetables_NewWay_prev( VegetableID int, Name sysname, WhenPicked datetime, BackStory nvarchar(max));GO CREATE TABLE dbo.Vegetables_NewWay_hold( VegetableID int, Name sysname, WhenPicked datetime, BackStory datetime);Vi skapar en procedur som läser in den mellanliggande kopian av tabellen och sedan använder en transaktion för att byta ut den aktuella kopian.
SKAPA PROCEDUR dbo.DoTheVeggieSwap_NewWayASBEGIN STÄLL IN NOCOUNT PÅ; TRUNCATE TABLE dbo.Vegetables_NewWay_prev; INSERT dbo.Vegetables_NewWay_prev SELECT TOP (1000000) s.session_id, o.name, s.last_successful_logon, LEFT(m.definition, 500) FROM sys.dm_exec_sessions AS s CROSS JOIN model.sys. all_sql_modules AS m ON o.[object_id] =m.[object_id]; -- måste ta Sch-M-lås här:BÖRJA TRANSAKTION; ALTER TABLE dbo.Vegetables_NewWay BYTTA TILL dbo.Vegetables_NewWay_hold MED (WAIT_AT_LOW_PRIORITY (MAX_DURATION =1 MINUTES, ABORT_AFTER_WAIT =BLOCKERS)); ÄNDRA TABELL dbo.Vegetables_NewWay_prev VÄXLA TILL dbo.Vegetables_NewWay; BETA TRANSAKTION; -- och nu kommer användare att fråga efter den nya datan i dbo -- kan byta tillbaka den gamla kopian och trunkera den -- utan att störa andra frågor ALTER TABLE dbo.Vegetables_NewWay_hold BYT TILL dbo.Vegetables_NewWay_prev; TRUNCATE TABLE dbo.Vegetables_NewWay_prev;ENDGOSkönheten med
WAIT_AT_LOW_PRIORITY
är att du helt kan kontrollera beteendet medABORT_AFTER_WAIT
alternativ:
ABORT_AFTER_WAIT inställning | Beskrivning/symptom |
---|---|
SJÄLV | Detta betyder att växeln kommer att ge upp efter n minuter. För sessionen som försöker utföra bytet kommer detta att dyka upp som felmeddelandet: Tidsgränsen för låsbegäran har överskridits. |
BLOCKERINGAR | Detta dikterar att omkopplaren väntar upp till n minuter, tvinga sig sedan fram i linjen genom att döda alla blockerare före den . Sessioner som försöker interagera med tabellen som stöts av växlingsoperationen kommer att se en kombination av dessa felmeddelanden: Din session har kopplats bort på grund av en DDL-operation med hög prioritet.Kan inte fortsätta körningen eftersom sessionen är i dödläge. Ett allvarligt fel inträffade på det aktuella kommandot. Resultaten, om några, bör kasseras. |
INGEN | Detta säger att omkopplaren glatt väntar tills den blir sin tur, oavsett MAX_DURATION .
Detta är samma beteende som du skulle få med byta namn, schemaöverföring eller partitionsväxel utan |
BLOCKERS
alternativet är inte det vänligaste sättet att hantera saker, eftersom du redan säger att det är okej genom denna iscensättning/växlingsoperation för användare att se data som är lite inaktuella. Jag skulle förmodligen föredra att använda SELF
och få operationen att försöka igen i fall där den inte kunde få de nödvändiga låsen inom den tilldelade tiden. Jag skulle dock hålla reda på hur ofta det misslyckas, särskilt på varandra följande misslyckanden, eftersom du vill se till att data aldrig blir för inaktuell.
Jämfört med det gamla sättet att växla mellan scheman
Så här skulle jag ha hanterat bytet innan:
ANVÄND OldWay;GO -- skapa två scheman och två kopior av tabellen CREATE SCHEMA prev AUTHORIZATION dbo;GO CREATE SCHEMA hold AUTHORIZATION dbo;GO CREATE TABLE dbo.Vegetables_OldWay( VegetableID int, Name sysname, WhenPicked date, WhenSticked date. max));GO CREATE TABLE prev.Vegetables_OldWay( VegetableID int, Name sysname, WhenPicked datetime, BackStory nvarchar(max));GO CREATE PROCEDURE dbo.DoTheVeggieSwap_OldWayASBEGIN SET NOCOUNT ON; TRUNCATE TABLE prev.Vegetables_OldWay; INSERT prev.Vegetables_OldWay SELECT TOP (1000000) s.session_id, o.name, s.last_successful_logon, LEFT(m.definition, 500) FRÅN sys.dm_exec_sessions AS s CROSS JOIN model.sys.all IN model.sys. all_sql_modules AS m ON o.[object_id] =m.[object_id]; -- måste ta Sch-M-lås här:BÖRJA TRANSAKTION; ALTER SCHEMA hold TRANSFER dbo.Vegetables_OldWay; ALTER SCHEMA dbo TRANSFER prev.Vegetables_OldWay; BETA TRANSAKTION; -- och nu kommer användare att fråga efter nya data i dbo -- kan överföra den gamla kopian tillbaka och trunkera den utan att -- störa andra frågor:ALTER SCHEMA prev TRANSFER hold.Vegetables_OldWay; TRUNCATE TABLE prev.Vegetables_OldWay;ENDGO
Jag körde samtidighetstest genom att använda två fönster av Erik Ejlskov Jensens SQLQueryStress:ett för att upprepa ett anrop till proceduren varje minut, och det andra för att köra 16 trådar som denna, tusentals gånger:
BÖRJA TRANSAKTIONEN; UPPDATERA TOP (1) dbo.
Längd och felfrekvens | Schema Transfer | ABORT_AFTER_WAIT: SJÄLV | ABORT_AFTER_WAIT: BLOCKERS |
---|---|---|---|
Genomsnittlig varaktighet – överföring/växling | 96,4 sekunder | 68,4 sekunder | 20,8 sekunder |
Genomsnittlig varaktighet – DML | 18,7 sekunder | 2,7 sekunder | 2,9 sekunder |
Undantag – Överför/Switch | 0 | 0,5/minut | 0 |
Undantag – DML | 0 | 0 | 25,5/minut |
Observera att varaktigheterna och antalet undantag kommer att vara mycket beroende av dina serverspecifikationer och vad som mer händer i din miljö. Observera också att även om det inte fanns några undantag för schemaöverföringstesterna när du använder SQLQueryStress, kan du få några mer strikta tidsgränser beroende på den konsumerande applikationen. Och det var så mycket långsammare i genomsnitt, eftersom blockeringen hopade sig mycket mer aggressivt. Ingen vill någonsin ha undantag, men när det finns en avvägning som denna, kanske du föredrar några undantag här och där (beroende på frekvensen av uppdateringsoperationen) framför att alla väntar längre hela tiden.
Partitionsbyte vs. Byt namn/schemaöverföring för att uppdatera SQL Server-tabeller
Partitionsbyte låter dig välja vilken del av din process som bär kostnaden för samtidighet. Du kan ge företräde åt bytesprocessen, så att data är mer tillförlitligt färsk, men det betyder att några av dina frågor kommer att misslyckas. Omvänt kan du prioritera frågorna, till priset av en långsammare uppdateringsprocess (och enstaka fel där). Huvuddragen är att SQL Server-partitionsbyte är en överlägsen metod för att uppdatera SQL Server-tabeller jämfört med de tidigare teknikerna för överföring av namn/schema på nästan alla punkter, och du kan använda mer robust logik för att försöka igen eller experimentera med varaktighetstoleranser för att landa på sweet spot för din arbetsbörda.