sql >> Databasteknik >  >> RDS >> Database

Minimal loggning med INSERT...SELECT in heap-tabeller

Introduktion

Att uppnå minimal loggning med INSERT...SELECT kan vara en komplicerad verksamhet. Övervägandena som listas i Data Loading Performance Guide är fortfarande ganska omfattande, även om man också behöver läsa SQL Server 2016, Minimal logging och Impact of the Batchsize i bulkbelastningsoperationer av Parikshit Savjani från SQL Server Tiger Team för att få den uppdaterade bilden för SQL Server 2016 och senare, vid massladdning i klustrade radlagringstabeller. Som sagt, den här artikeln handlar bara om att tillhandahålla nya detaljer om minimal loggning vid massladdning av traditionella (inte "minnesoptimerade") högtabeller med INSERT...SELECT . Tabeller med ett b-träd klustrade index täcks separat i del två av denna serie.

Högtabeller

När du infogar rader med INSERT...SELECT i en hög utan icke-klustrade index, säger dokumentationen universellt att sådana inlägg kommer att loggas minimalt så länge som en TABLOCK ledtråd finns. Detta återspeglas i sammanfattningstabellerna i Data Loading Performance Guide och Tiger Team-posten. Sammanfattningsraderna för heaptabeller utan index är desamma i båda dokumenten (inga ändringar för SQL Server 2016):

En explicit TABLOCK tips är inte det enda sättet att uppfylla kravet på bordsnivålåsning . Vi kan också ställa in "bordslås vid bulklast" alternativ för måltabellen med sp_tableoption eller genom att aktivera dokumenterad spårningsflagga 715. (Obs:Dessa alternativ är inte tillräckliga för att möjliggöra minimal loggning när du använder INSERT...SELECT eftersom INSERT...SELECT stöder inte massuppdateringslås).

"samtidigt möjligt" kolumnen i sammanfattningen gäller endast för bulkladdningsmetoder andra än INSERT...SELECT . Samtidig laddning av en heaptabell är inte möjlig med INSERT...SELECT . Som nämnts i Data Loading Performance Guide , massladdning med INSERT...SELECT tar en exklusiv X lås på bordet, inte massauppdateringen BU lås krävs för samtidiga bulklaster.

Bortsett från allt detta – och förutsatt att det inte finns någon annan anledning att inte förvänta sig minimal loggning när en oindexerad hög med TABLOCK masslastas in. (eller motsvarande) — infogningen kanske inte vara minimalt loggad...

Ett undantag från regeln

Följande demoskript bör köras på en utvecklingsinstans i en ny testdatabas inställd på att använda SIMPLE återhämtningsmodell. Den laddar ett antal rader i en heaptabell med INSERT...SELECT med TABLOCK , och rapporter om transaktionsloggposterna som genereras:

SKAPA TABELL dbo.TestHeap( id heltal NOT NULL IDENTITY, c1 heltal NOT NULL, padding char(45) NOT NULL DEFAULT '');GO-- Rensa loggenCHECKPOINT;GO-- Infoga raderINSERT dbo.TestHeap WITH (TABLOCK) ) (c1)SELECT TOP (897) CHECKSUM(NEWID())FRÅN master.dbo.spt_values ​​AS SV;GO-- Visa loggposterVÄLJ FD.Operation, FD.Context, FD.[Log Record Length], FD.[Log Reserve], FD.AllocUnitName, FD.[Transaktionsnamn], FD.[Låsinformation], FD.[Description]FROM sys.fn_dblog(NULL, NULL) AS FD;GO-- Räkna antalet helt loggade raderSELECT [ Fullt loggade rader] =COUNT_BIG(*) FRÅN sys.fn_dblog(NULL, NULL) SOM FDWHERE FD.Operation =N'LOP_INSERT_ROWS' OCH FD.Context =N'LCX_HEAP' OCH FD.AllocUnitName =N'dbo. 

Utdata visar att alla 897 rader var fullständigt loggade trots att de uppenbarligen uppfyller alla villkor för minimal loggning (bara ett urval av loggposter visas av utrymmesskäl):

Samma resultat ses om insättningen upprepas (dvs. det spelar ingen roll om högbordet är tomt eller inte). Detta resultat motsäger dokumentationen.

Den minimala loggningströskeln för heaps

Antalet rader man behöver lägga till i en enda INSERT...SELECT uttalande för att uppnå minimal loggning till en oindexerad hög med tabelllåsning aktiverad beror på en beräkning som SQL Server utför vid uppskattning av den totala storleken av de uppgifter som ska infogas. Indata till denna beräkning är:

  • Versionen av SQL Server.
  • Det uppskattade antalet rader som leder till Infoga operatör.
  • Måltabellsradstorlek.

För SQL Server 2012 och tidigare , övergångspunkten för den här specifika tabellen är 898 rader . Ändra numret i demoskriptet TOP sats från 897 till 898 ger följande utdata:

Transaktionsloggposterna som genereras handlar om sidallokering och underhåll av Indexallokeringskarta (IAM) och Sidfritt utrymme (PFS) strukturer. Kom ihåg att minimal loggning innebär att SQL Server inte loggar varje radinfogning individuellt. Istället loggas endast ändringar av metadata och allokeringsstrukturer. Ändring från 897 till 898 rader möjliggör minimal loggning för denna specifika tabell.

För SQL Server 2014 och senare , är övergångspunkten 950 rader för detta bord. Kör INSERT...SELECT med TOP (949) kommer att använda fullständig loggning – byter till TOP (950) kommer att producera minimal loggning .

Tröskelvärdena är inte beroende på kardinalitetsuppskattningen modell som används eller databaskompatibilitetsnivån.

Datastorleksberäkningen

Huruvida SQL Server bestämmer sig för att använda raduppsättning bulk load — och därför om minimal loggning är tillgänglig eller inte — beror på resultatet av en serie beräkningar utförda i en metod som heter sqllang!CUpdUtil::FOptimizeInsert , som antingen returnerar true för minimal loggning, eller falskt för fullständig loggning. Ett exempel på samtalsstack visas nedan:

Kärnan i testet är:

  • Infogningen måste vara för mer än 250 rader .
  • Den totala infogningsdatastorleken måste beräknas som minst 8 sidor .

Kontrollen för mer än 250 rader beror enbart på det uppskattade antalet rader som kommer till Table Insert operatör. Detta visas i exekveringsplanen som 'Uppskattat antal rader' . Var försiktig med detta. Det är lätt att ta fram en plan med ett lågt beräknat antal rader, till exempel genom att använda en variabel i TOP sats utan OPTION (RECOMPILE) . I så fall gissar optimeraren på 100 rader, vilket inte kommer att nå tröskeln, och på så sätt förhindra bulkbelastning och minimal loggning.

Beräkningen av total datastorlek är mer komplex och stämmer inte överens "Beräknad radstorlek" flödar in i Table Insert operatör. Sättet som beräkningen utförs på är något annorlunda i SQL Server 2012 och tidigare jämfört med SQL Server 2014 och senare. Ändå ger båda ett resultat av radstorlek som skiljer sig från vad som visas i utförandeplanen.

Radstorleksberäkningen

Den totala infogningsdatastorleken beräknas genom att multiplicera det uppskattade antalet rader med förväntad maximal radstorlek . Beräkningen av radstorleken är den punkt som skiljer sig åt mellan SQL Server-versioner.

I SQL Server 2012 och tidigare utförs beräkningen av sqllang!OptimizerUtil::ComputeRowLength . För testhögtabellen (avsiktligt utformad med enkla kolumner utan noll med fast längd med den ursprungliga FixedVar radlagringsformat) en översikt över beräkningen är:

  • Initiera en FixedVar metadatagenerator.
  • Hämta typ- och attributinformation för varje kolumn i Tabellinfoga ingångsström.
  • Lägg till skrivna kolumner och attribut till metadata.
  • Slutför generatorn och be den om maximal radstorlek.
  • Lägg till overhead för nullbitmappen och antal kolumner.
  • Lägg till fyra byte för raden statusbitar och radförskjutning till antalet kolumnerdata.

Fysisk radstorlek

Resultatet av denna beräkning kan förväntas matcha den fysiska radstorleken, men det gör det inte. Till exempel, med radversionering avstängd för databasen:

VÄLJ DDIPS.index_type_desc, DDIPS.alloc_unit_type_desc, DDIPS.page_count, DDIPS.record_count, DDIPS.min_record_size_in_bytes, DDIPS.max_record_size_in_bytes, DDIPS.avg_record_size_in_s_stat,'OBTROM ,N_JTROM,N_Bydcal(n_dcal_in_bytes,'Obdcal/ U'), 0, -- heap NULL, -- alla partitioner 'DETAILED' ) SOM DDIPS;

…ger en rekordstorlek på 60 byte i varje rad i testtabellen:

Detta är som beskrivs i Uppskatta storleken på en hög:

  • Total bytestorlek för alla fast längd kolumner =53 byte:
    • id integer NOT NULL =4 byte
    • c1 integer NOT NULL =4 byte
    • padding char(45) NOT NULL =45 byte.
  • Noll bitmapp =3 byte :
    • =2 + int((Num_Cols + 7) / 8)
    • =2 + int((3 + 7) / 8)
    • =3 byte.
  • Radhuvud =4 byte .
  • Totalt 53 + 3 + 4 =60 byte .

Den matchar också den beräknade radstorleken som visas i exekveringsplanen:

Interna beräkningsdetaljer

Den interna beräkningen som används för att avgöra om bulklast används ger ett annat resultat, baserat på följande infoga ström kolumninformation erhållen med en debugger. Typnumren som används matchar sys.types :

  • Total fast längd kolumnstorlek =66 byte :
    • Typ id 173 binary(8) =8 byte (internt).
    • Typ id 56 integer =4 byte (internt).
    • Typ id 104 bit =1 byte (intern).
    • Typ id 56 integer =4 byte (id kolumn).
    • Typ id 56 integer =4 byte (c1 kolumn).
    • Typ id 175 char(45) =45 byte (padding kolumn).
  • Noll bitmapp =3 byte (som tidigare).
  • Radhuvud overhead =4 byte (som tidigare).
  • Beräknad radstorlek =66 + 3 + 4 =73 byte .

Skillnaden är att ingångsströmmen matar Table Insert operatorn innehåller tre extra interna kolumner . Dessa tas bort när showplanen genereras. De extra kolumnerna utgör tabellinfogningslokalisatorn , som inkluderar bokmärket (RID eller radlokalisering) som sin första komponent. Det är metadata för insatsen och läggs inte till i tabellen.

De extra kolumnerna förklarar skillnaden mellan beräkningen som utförs av OptimizerUtil::ComputeRowLength och den fysiska storleken på raderna. Detta kan ses som en bugg :SQL Server ska inte räkna metadatakolumner i infogningsströmmen mot den slutliga fysiska storleken på raden. Å andra sidan kan beräkningen helt enkelt vara en uppskattning av bästa ansträngning med den generiska uppdateringen operatör.

Beräkningen tar inte heller hänsyn till andra faktorer som 14-byte overhead för radversionering. Detta kan testas genom att köra demoskriptet igen med någon av ögonblicksbildsisoleringarna eller läs engagerad ögonblicksbildsisolering databasalternativ aktiverade. Den fysiska storleken på raden kommer att öka med 14 byte (från 60 byte till 74), men tröskeln för minimal loggning förblir oförändrad på 898 rader.

Tröskelberäkning

Vi har nu alla detaljer vi behöver för att se varför tröskeln är 898 rader för den här tabellen på SQL Server 2012 och tidigare:

  • 898 rader uppfyller det första kravet för mer än 250 rader .
  • Beräknad radstorlek =73 byte.
  • Uppskattat antal rader =897.
  • Total datastorlek =73 byte * 897 rader =65481 byte.
  • Totalt antal sidor =65481 / 8192 =7,9932861328125.
    • Detta är strax under det andra kravet på>=8 sidor.
  • För 898 rader är antalet sidor 8,002197265625.
    • Detta är >=8 sidorminimal loggning är aktiverad.

I SQL Server 2014 och senare , ändringarna är:

  • Radstorleken beräknas av metadatageneratorn.
  • Den interna heltalskolumnen i tabellsökaren finns inte längre i infogningsströmmen. Detta representerar uniquiifier , som endast gäller index. Det verkar troligt att detta togs bort som en buggfix.
  • Den förväntade radstorleken ändras från 73 till 69 byte på grund av den utelämnade heltalskolumnen (4 byte).
  • Den fysiska storleken är fortfarande 60 byte. Den återstående skillnaden på 9 byte står för den extra 8-byte RID och 1-byte bitars interna kolumner i infogningsströmmen.

För att nå tröskeln på 8 sidor med 69 byte per rad:

  • 8 sidor * 8192 byte per sida =65536 byte.
  • 65535 byte / 69 byte per rad =949,7971014492754 rader.
  • Vi förväntar oss därför minst 950 rader för att aktivera raduppsättning bulkbelastning för den här tabellen på SQL Server 2014 och framåt.

Sammanfattning och slutliga tankar

I motsats till bulkladdningsmetoderna som stöder batchstorlek , enligt inlägget av Parikshit Savjani, INSERT...SELECT till en oindexerad hög (tom eller inte) inte alltid resultera i minimal loggning när tabelllåsning anges.

För att möjliggöra minimal loggning med INSERT...SELECT , SQL Server måste förvänta sig fler än 250 rader med en total storlek på minst en omfattning (8 sidor).

Vid beräkning av den uppskattade totala infogningsstorleken (för att jämföra med tröskeln på 8 sidor), multiplicerar SQL Server det uppskattade antalet rader med en beräknad maximal radstorlek. SQL Server räknas interna kolumner finns i infogningsströmmen när radstorleken beräknas. För SQL Server 2012 och tidigare lägger detta till 13 byte per rad. För SQL Server 2014 och senare lägger den till 9 byte per rad. Detta påverkar bara beräkningen; det påverkar inte den slutliga fysiska storleken på raderna.

När minimalt loggad heap-bulkbelastning är aktiv, gör SQL Server det inte infoga rader en i taget. Omfattningar tilldelas i förväg och rader som ska infogas samlas in på helt nya sidor av sqlmin!RowsetBulk innan de läggs till den befintliga strukturen. Ett exempel på samtalsstack visas nedan:

Logiska läsningar rapporteras inte för måltabellen när minimalt loggad högbulklast används – Tabellinfoga operatören behöver inte läsa en befintlig sida för att hitta insättningspunkten för varje ny rad.

Exekveringsplaner för närvarande visas inte hur många rader eller sidor som infogades med raduppsättning massladdning och minimal loggning . Kanske kommer denna användbara information att läggas till produkten i en framtida version.


  1. Hur får man en lista över MySQL-vyer?

  2. Tillstånd nekad för relation

  3. SQL Server beroenden

  4. FORALL-uttalande med nedre och övre gräns i Oracle-databasen