Introduktion
Opnå minimal logning med INSERT...SELECT
kan være en kompliceret forretning. Overvejelserne i Data Loading Performance Guide er stadig ret omfattende, selvom man også skal læse SQL Server 2016, Minimal logging og Impact of the Batchsize i bulk load-operationer af Parikshit Savjani fra SQL Server Tiger Team for at få det opdaterede billede for SQL Server 2016 og nyere, ved masseindlæsning i klyngede rowstore-tabeller. Når det er sagt, handler denne artikel udelukkende om at give nye detaljer om minimal logning ved masseindlæsning af traditionelle (ikke "hukommelsesoptimerede") heap-tabeller ved hjælp af INSERT...SELECT
. Tabeller med et b-træ klynget indeks er dækket separat i del to af denne serie.
Dyngetabeller
Når du indsætter rækker ved hjælp af INSERT...SELECT
i en bunke uden ikke-klyngede indekser, siger dokumentationen universelt, at sådanne inserts vil blive minimalt logget så længe som en TABLOCK
hint er til stede. Dette afspejles i oversigtstabellerne i Data Loading Performance Guide og Tiger Team-posten. Opsummeringsrækkerne for heaptabeller uden indeks er de samme i begge dokumenter (ingen ændringer for SQL Server 2016):
En eksplicit TABLOCK
tip er ikke den eneste måde at opfylde kravet om bord-niveaulåsning . Vi kan også indstille 'bordlås ved bulkbelastning' mulighed for måltabellen ved hjælp af sp_tableoption
eller ved at aktivere dokumenteret sporingsflag 715. (Bemærk:Disse muligheder er ikke tilstrækkelige til at aktivere minimal logning ved brug af INSERT...SELECT
fordi INSERT...SELECT
understøtter ikke masseopdateringslåse).
"samtidig mulig" kolonnen i oversigten gælder kun for andre bulk loading metoder end INSERT...SELECT
. Samtidig indlæsning af en heap-tabel er ikke mulig med INSERT...SELECT
. Som nævnt i Data Loading Performance Guide , masseindlæsning med INSERT...SELECT
tager en eksklusiv X
lås på bordet, ikke masseopdateringen BU
lås påkrævet for samtidige bulklaster.
Alt det til side – og forudsat at der ikke er nogen anden grund til ikke at forvente minimal logning, når en uindekseret bunke masseindlæses med TABLOCK
(eller tilsvarende) — indsættelsen måske stadig ikke være minimalt logget...
En undtagelse fra reglen
Følgende demoscript skal køres på en udviklingsforekomst i en ny testdatabase indstillet til at bruge SIMPLE
recovery model. Den indlæser et antal rækker i en heap-tabel ved hjælp af INSERT...SELECT
med TABLOCK
, og rapporter om de genererede transaktionslogposter:
CREATE TABLE dbo.TestHeap( id integer NOT NULL IDENTITY, c1 integer NOT NULL, padding char(45) NOT NULL DEFAULT '');GO-- Ryd logCHECKPOINT;GO-- Insert rowsINSERT dbo.TestHeap WITH (TABLOCK) ) (c1)VÆLG TOP (897) CHECKSUM(NEWID())FRA master.dbo.spt_values AS SV;GO-- Vis logposterVÆLG FD.Operation, FD.Context, FD.[Logpostlængde], FD.[Log Reserve], FD.AllocUnitName, FD.[Transaktionsnavn], FD.[Låsinformation], FD.[Description]FROM sys.fn_dblog(NULL, NULL) AS FD;GO-- Tæl antallet af fuldt loggede rækkerSELECT [ Fuldt loggede rækker] =COUNT_BIG(*) FRA sys.fn_dblog(NULL, NULL) AS FDWHERE FD.Operation =N'LOP_INSERT_ROWS' OG FD.Context =N'LCX_HEAP' OG FD.AllocUnitName =N'dbo.Outputtet viser, at alle 897 rækker var fuldt logget på trods af at de tilsyneladende opfylder alle betingelserne for minimal logning (kun et udsnit af logposter vises af pladshensyn):
Det samme resultat ses, hvis indsatsen gentages (dvs. det er ligegyldigt, om bunkebordet er tomt eller ej). Dette resultat er i modstrid med dokumentationen.
Den minimale logningstærskel for dynger
Antallet af rækker, man skal tilføje i en enkelt
INSERT...SELECT
erklæring for at opnå minimal logning ind i en uindekseret bunke med tabellåsning aktiveret afhænger af en beregning, som SQL Server udfører ved estimering af den samlede størrelse af de data, der skal indsættes. Indgangene til denne beregning er:
- Versionen af SQL Server.
- Det estimerede antal rækker, der fører ind i Indsæt operatør.
- Måltabel rækkestørrelse.
Til SQL Server 2012 og tidligere , overgangspunktet for denne særlige tabel er 898 rækker . Ændring af nummeret i demoscriptet
TOP
klausul fra 897 til 898 producerer følgende output:
De genererede transaktionslogposter vedrører sideallokering og vedligeholdelse af Index Allocation Map (IAM) og Page ledig plads (PFS) strukturer. Husk at minimal logning betyder, at SQL Server ikke logger hver rækkeindsættelse individuelt. I stedet logges kun ændringer af metadata og allokeringsstrukturer. Ændring fra 897 til 898 rækker aktiverer minimal logning for denne specifikke tabel.
Til SQL Server 2014 og nyere , overgangspunktet er 950 rækker for dette bord. Kører
INSERT...SELECT
medTOP (949)
vil bruge fuld logning – skifter tilTOP (950)
vil producere minimal logning .Grænserne er ikke afhængig af kardinalitetsestimatet model i brug eller databasekompatibilitetsniveau.
Datastørrelsesberegningen
Hvorvidt SQL Server beslutter sig for at bruge rækkesæt bulk load — og derfor om minimal logning er tilgængelig eller ej — afhænger af resultatet af en række beregninger udført i en metode kaldet
sqllang!CUpdUtil::FOptimizeInsert
, som enten returnerer sand for minimal logning eller falsk for fuld logning. Et eksempel på opkaldsstak er vist nedenfor:
Essensen af testen er:
- Indsætningen skal være for mere end 250 rækker .
- Den samlede indsættelsesdatastørrelse skal beregnes som mindst 8 sider .
Checken for mere end 250 rækker afhænger udelukkende af det anslåede antal rækker, der ankommer til Tabelindsættelsen operatør. Dette vises i udførelsesplanen som 'Anslået antal rækker' . Vær forsigtig med dette. Det er nemt at lave en plan med et lavt estimeret antal rækker, for eksempel ved at bruge en variabel i
TOP
klausul udenOPTION (RECOMPILE)
. I så fald gætter optimeringsværktøjet på 100 rækker, hvilket ikke vil nå tærsklen, og dermed forhindre massebelastning og minimal logning.Beregningen af den samlede datastørrelse er mere kompleks og matcher ikke 'Anslået rækkestørrelse' flyder ind i Tabelindsæt operatør. Den måde, beregningen udføres på, er lidt anderledes i SQL Server 2012 og tidligere sammenlignet med SQL Server 2014 og senere. Alligevel producerer begge et rækkestørrelsesresultat, der er anderledes end det, der ses i udførelsesplanen.
Rækkestørrelsesberegningen
Den samlede indsættelsesdatastørrelse beregnes ved at gange det estimerede antal rækker efter den forventede maksimale rækkestørrelse . Rækkestørrelsesberegningen er det punkt, der adskiller sig mellem SQL Server-versioner.
I SQL Server 2012 og tidligere udføres beregningen af
sqllang!OptimizerUtil::ComputeRowLength
. Til testbunketabellen (bevidst designet med simple fastlængde ikke-nul kolonner ved hjælp af den originale FixedVar rækkelagerformat) en oversigt over beregningen er:
- Initialiser en FixedVar metadatagenerator.
- Få type- og attributoplysninger for hver kolonne i Tabelindsæt inputstrøm.
- Tilføj indtastede kolonner og attributter til metadataene.
- Afslut generatoren, og bed den om den maksimale rækkestørrelse.
- Tilføj overhead for nul bitmap og antal kolonner.
- Tilføj fire bytes til rækkens statusbits og rækkeforskydning til antallet af kolonnedata.
Fysisk rækkestørrelse
Resultatet af denne beregning kan forventes at matche den fysiske rækkestørrelse, men det gør det ikke. For eksempel, med rækkeversionering slået fra for databasen:
VÆLG 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_bytes,'0 U'), 0, -- heap NULL, -- alle partitioner 'DETAILED' ) SOM DDIPS;…giver en rekordstørrelse på 60 bytes i hver række i testtabellen:
Dette er som beskrevet i Estimer størrelsen af en bunke:
- Samlet bytestørrelse for alle fast længde kolonner =53 bytes:
id integer NOT NULL
=4 bytesc1 integer NOT NULL
=4 bytespadding char(45) NOT NULL
=45 bytes.- Nul bitmap =3 bytes :
- =2 + int((Num_Cols + 7) / 8)
- =2 + int((3 + 7) / 8)
- =3 bytes.
- Rækkeoverskrift =4 bytes .
- I alt 53 + 3 + 4 =60 bytes .
Det matcher også den estimerede rækkestørrelse vist i udførelsesplanen:
Interne beregningsdetaljer
Den interne beregning, der bruges til at bestemme, om der bruges bulkbelastning, giver et andet resultat baseret på følgende indsæt strøm kolonneoplysninger opnået ved hjælp af en debugger. De anvendte typenumre matcher
sys.types
:
- Samlet fast længde kolonnestørrelse =66 bytes :
- Typ id 173
binary(8)
=8 bytes (internt).- Typ id 56
integer
=4 bytes (internt).- Typ id 104
bit
=1 byte (intern).- Typ id 56
integer
=4 bytes (id
kolonne).- Typ id 56
integer
=4 bytes (c1
kolonne).- Typ id 175
char(45)
=45 bytes (padding
kolonne).- Nul bitmap =3 bytes (som før).
- Rækkeoverskrift overhead =4 bytes (som før).
- Beregnet rækkestørrelse =66 + 3 + 4 =73 bytes .
Forskellen er, at inputstrømmen leverer Tabelindsættelsen operatoren indeholder tre ekstra interne kolonner . Disse fjernes, når showplanen genereres. De ekstra kolonner udgør tabelindsættelseslokalisatoren , som inkluderer bogmærket (RID eller rækkefinder) som sin første komponent. Det er metadata for indsatsen og ender ikke med at blive tilføjet til tabellen.
De ekstra kolonner forklarer uoverensstemmelsen mellem beregningen udført af
OptimizerUtil::ComputeRowLength
og rækkernes fysiske størrelse. Dette kunne ses som en fejl :SQL Server bør ikke tælle metadatakolonner i indsættelsesstrømmen mod den endelige fysiske størrelse af rækken. På den anden side kan beregningen simpelthen være et estimat for bedste indsats ved hjælp af den generiske opdatering operatør.Beregningen tager heller ikke højde for andre faktorer som 14-byte overhead af rækkeversionering. Dette kan testes ved at køre demoscriptet igen med en af snapshot-isolationerne eller læs engageret snapshot-isolation databaseindstillinger aktiveret. Den fysiske størrelse af rækken vil øges med 14 bytes (fra 60 bytes til 74), men tærsklen for minimal logning forbliver uændret på 898 rækker.
Tærskelberegning
Vi har nu alle de detaljer, vi skal bruge for at se, hvorfor tærsklen er 898 rækker for denne tabel på SQL Server 2012 og tidligere:
- 898 rækker opfylder det første krav om mere end 250 rækker .
- Beregnet rækkestørrelse =73 bytes.
- Anslået antal rækker =897.
- Samlet datastørrelse =73 bytes * 897 rækker =65481 bytes.
- Sider i alt =65481 / 8192 =7,9932861328125.
- Dette er lige under det andet krav for>=8 sider.
- For 898 rækker er antallet af sider 8,002197265625.
- Dette er >=8 sider så minimal logning er aktiveret.
I SQL Server 2014 og nyere , ændringerne er:
- Rækkestørrelsen beregnes af metadatageneratoren.
- Den interne heltalskolonne i tabelfinderen er ikke længere til stede i indsætningsstrømmen. Dette repræsenterer uniquifier , som kun gælder for indekser. Det ser ud til, at dette er blevet fjernet som en fejlrettelse.
- Den forventede rækkestørrelse ændres fra 73 til 69 bytes på grund af den udeladte heltalskolonne (4 bytes).
- Den fysiske størrelse er stadig 60 bytes. Den resterende forskel på 9 bytes står for de ekstra 8-byte RID og 1-byte bit interne kolonner i indsættelsesstrømmen.
For at nå tærsklen på 8 sider med 69 bytes pr. række:
- 8 sider * 8192 bytes pr. side =65536 bytes.
- 65535 bytes / 69 bytes pr. række =949,7971014492754 rækker.
- Vi forventer derfor et minimum på 950 rækker for at aktivere masseindlæsning af rækkesæt for denne tabel på SQL Server 2014 og frem.
Opsummering og endelige tanker
I modsætning til masseindlæsningsmetoderne, der understøtter batchstørrelse , som dækket i indlægget af Parikshit Savjani,
INSERT...SELECT
i en uindekseret bunke (tom eller ej) ikke altid resultere i minimal logning, når tabellåsning er angivet.For at aktivere minimal logning med
INSERT...SELECT
, skal SQL Server forvente mere end 250 rækker med en samlet størrelse på mindst ét omfang (8 sider).Ved beregning af den estimerede samlede indsættelsesstørrelse (for at sammenligne med tærsklen på 8 sider), multiplicerer SQL Server det estimerede antal rækker med en beregnet maksimal rækkestørrelse. SQL Server tæller interne kolonner til stede i indsætningsstrømmen, når rækkestørrelsen beregnes. For SQL Server 2012 og tidligere tilføjer dette 13 bytes pr. række. For SQL Server 2014 og nyere tilføjer den 9 bytes pr. række. Dette påvirker kun beregningen; det påvirker ikke den endelige fysiske størrelse af rækkerne.
Når minimalt logget heap bulk load er aktiv, gør SQL Server ikke det indsæt rækker en ad gangen. Omfang tildeles på forhånd, og rækker, der skal indsættes, samles på helt nye sider af
sqlmin!RowsetBulk
før de føjes til den eksisterende struktur. Et eksempel på opkaldsstak er vist nedenfor:
Logiske læsninger er ikke rapporteret for måltabellen, når der bruges minimalt logget bunkelast – Tabelindsæt operatør behøver ikke at læse en eksisterende side for at finde indsættelsespunktet for hver ny række.
Udførelsesplaner vises ikke i øjeblikket hvor mange rækker eller sider, der blev indsat ved brug af masseindlæsning af rækkesæt og minimal logning . Måske vil disse nyttige oplysninger blive føjet til produktet i en fremtidig udgivelse.