Read uncommitted er det svageste af de fire transaktionsisolationsniveauer, der er defineret i SQL Standard (og af de seks implementeret i SQL Server). Det tillader alle tre såkaldte "samtidighedsfænomener", dirty reads , ikke-gentagelige læsninger , og fantomer:
De fleste databasefolk er opmærksomme på disse fænomener, i det mindste i hovedtræk, men ikke alle er klar over, at de ikke fuldt ud beskriver de isolationsgarantier, der tilbydes; De beskriver heller ikke intuitivt den forskellige adfærd, man kan forvente i en specifik implementering som SQL Server. Mere om det senere.
Transaktionsisolering – 'jeget' i ACID
Hver SQL-kommando udføres inden for en transaktion (eksplicit, implicit eller auto-commit). Hver transaktion har et tilknyttet isolationsniveau, som bestemmer, hvor isoleret den er fra virkningerne af andre samtidige transaktioner. Dette noget tekniske koncept har vigtige konsekvenser for den måde, forespørgsler udføres på, og kvaliteten af de resultater, de producerer.
Overvej en simpel forespørgsel, der tæller alle rækkerne i en tabel. Hvis denne forespørgsel kunne udføres øjeblikkeligt (eller med nul samtidige dataændringer), kunne der kun være ét korrekt svar:antallet af rækker, der fysisk er til stede i tabellen på det tidspunkt. I virkeligheden vil det tage en vis tid at udføre forespørgslen, og resultatet vil afhænge af, hvor mange rækker eksekveringsmotoren faktisk støder på, når den krydser den fysiske struktur, der er valgt for at få adgang til dataene.
Hvis rækker tilføjes til (eller slettes fra) tabellen ved samtidige transaktioner, mens tælleoperationen er i gang, kan der opnås forskellige resultater afhængigt af, om rækkeoptællingstransaktionen støder på alle, nogle eller ingen af disse samtidige ændringer - hvilket afhænger igen af isolationsniveauet for rækkeoptællingstransaktionen.
Afhængigt af isolationsniveau, fysiske detaljer og timing af de samtidige operationer, kunne vores tælletransaktion endda producere et resultat, der aldrig var en sand afspejling af bordets forpligtede tilstand på noget tidspunkt under transaktionen.
Eksempel
Overvej en rækketællingstransaktion, der starter på tidspunktet T1 og scanner tabellen fra start til slut (i grupperet indeksnøglerækkefølge, for argumentets skyld). På det tidspunkt er der 100 forpligtede rækker i tabellen. Nogen tid senere (på tidspunktet T2), har vores tælletransaktion stødt på 50 af disse rækker. I samme øjeblik indsætter en samtidig transaktion to rækker i tabellen og commiterer kort tid senere på tidspunktet T3 (før tælletransaktionen slutter). En af de indsatte rækker falder tilfældigvis inden for halvdelen af den klyngede indeksstruktur, som vores tælletransaktion allerede har behandlet, mens den anden indsatte række sidder i den ikke-optalte del.
Når rækkeoptællingstransaktionen er fuldført, vil den rapportere 101 rækker i dette scenarie; 100 rækker til at begynde med i tabellen plus den enkelte indsatte række, der blev fundet under scanningen. Dette resultat er i modstrid med tabellens forpligtede historie:der var 100 forpligtede rækker på tidspunkterne T1 og T2, derefter 102 forpligtede rækker på tidspunktet T3. Der var aldrig et tidspunkt, hvor der var 101 forpligtede rækker.
Det overraskende (måske, afhængigt af hvor dybt du har tænkt over disse ting før) er, at dette resultat er muligt på standard (låsende) læseforpligtet isolationsniveau og endda under gentagelig læseisolering. Begge disse isolationsniveauer er garanteret kun at læse forpligtede data, men alligevel opnåede vi et resultat, der ikke repræsenterer nogen forpligtet tilstand af databasen!
Analyse
Det eneste transaktionsisoleringsniveau, der giver fuldstændig isolation fra samtidighedseffekter, kan serialiseres. SQL Server-implementeringen af det serialiserbare isolationsniveau betyder, at en transaktion vil se de seneste forpligtede data fra det øjeblik, dataene først blev låst for adgang. Derudover er det datasæt, der stødes på under serialiserbar isolation, garanteret ikke at ændre sit medlemskab, før transaktionen slutter.
Eksemplet med rækkeoptælling fremhæver et grundlæggende aspekt af databaseteori:vi skal være klare over, hvad et "korrekt" resultat betyder for en database, der oplever samtidige ændringer, og vi skal forstå de afvejninger, vi foretager, når vi vælger en isolation. niveau lavere end serialiserbart.
Hvis vi har brug for et punkt-i-tidsbillede af databasens forpligtede tilstand, bør vi bruge snapshot-isolation (for garantier på transaktionsniveau) eller læse forpligtede snapshot-isolering (for garantier på opgørelsesniveau). Bemærk dog, at en punkt-i-tidsvisning betyder, at vi ikke nødvendigvis opererer på den aktuelle forpligtede tilstand af databasen; faktisk kan vi bruge forældede oplysninger. På den anden side, hvis vi er tilfredse med resultater, der kun er baseret på forpligtede data (omend muligvis fra forskellige tidspunkter), kunne vi vælge at holde fast i standardlåsningsniveauet for læseforpligtet isolation.
For at være sikre på at producere resultater (og træffe beslutninger!) baseret på det seneste sæt af forpligtede data, for nogle seriel historie af operationer mod databasen, ville vi have brug for serialiserbar transaktionsisolering. Selvfølgelig er denne mulighed typisk den dyreste med hensyn til ressourceforbrug og reduceret samtidighed (inklusive en øget risiko for dødvande).
I eksemplet med rækkeoptælling ville begge snapshot-isolationsniveauer (SI og RCSI) give et resultat på 100 rækker, hvilket repræsenterer antallet af forpligtede rækker ved starten af erklæringen (og transaktion i dette tilfælde). Kørsel af forespørgslen ved låsning af læseforpligtet eller gentaget læseisolering kan give et resultat på 100, 101 eller 102 rækker – afhængigt af timing, låsegranularitet, rækkeindsættelsesposition og den valgte fysiske adgangsmetode. Under serialiserbar isolation ville resultatet være enten 100 eller 102 rækker, afhængigt af hvilken af de to samtidige transaktioner, der anses for at være udført først.
Hvor dårligt læses uengageret?
Efter at have introduceret læst uforpligtet isolation som det svageste af de tilgængelige isolationsniveauer, bør du forvente, at det giver endnu lavere isolationsgarantier end låsning af læst forpligtet (det næsthøjeste isolationsniveau). Det gør den faktisk; men spørgsmålet er:hvor meget værre end at låse læst forpligtet isolation er det?
For at vi starter med den korrekte kontekst, er her en liste over de vigtigste samtidighedseffekter, der kan opleves under SQL Server-standardlåsen for læst forpligtet isolationsniveau:
- Manglende forpligtede rækker
- Rækker stødt på flere gange
- Forskellige versioner af den samme række stødt på i en enkelt erklæring/forespørgselsplan
- Forpligtede kolonnedata fra forskellige tidspunkter i samme række (eksempel)
Disse samtidighedseffekter skyldes alle, at låseimplementeringen af read committed kun tager meget kortsigtede delte låse ved læsning af data. Det læste uforpligtende isolationsniveau går et skridt videre, ved slet ikke at tage delte låse, hvilket resulterer i den yderligere mulighed for "beskidte læsninger."
Dirty Reads
Som en hurtig påmindelse refererer en "beskidt læsning" til læsning af data, der bliver ændret af en anden samtidig transaktion (hvor "ændring" inkorporerer indsættelses-, opdaterings-, sletnings- og fletoperationer). Sagt på en anden måde opstår en beskidt læsning, når en transaktion læser data, som en anden transaktion har ændret, før den ændrende transaktion har begået eller afbrudt disse ændringer.
Fordele og ulemper
De primære fordele ved læst uforpligtende isolation er det reducerede potentiale for blokering og deadlocking på grund af inkompatible låse (herunder unødvendig blokering på grund af låseeskalering) og muligvis øget ydeevne (ved at undgå behovet for at erhverve og frigive delte låse).
Den mest åbenlyse potentielle ulempe ved læst uforpligtende isolation er (som navnet antyder), at vi måske læser ikke-forpligtede data (selv data der aldrig er). begået, i tilfælde af en tilbagerulning af transaktionen). I en database, hvor tilbagerulninger er relativt sjældne, kan spørgsmålet om at læse ikke-forpligtede data ses som et blot timing-spørgsmål, da de pågældende data helt sikkert vil blive begået på et tidspunkt, og sandsynligvis ganske snart. Vi har allerede set timing-relaterede uoverensstemmelser i eksemplet med rækkeoptælling (som fungerede på et højere isolationsniveau), så man kan godt stille spørgsmålstegn ved, hvor meget det er bekymrende at læse data "for tidligt."
Svaret afhænger klart af lokale prioriteter og kontekst, men en informeret beslutning om at bruge læst uforpligtende isolation synes bestemt mulig. Der er dog mere at tænke på. SQL Server-implementeringen af læse-uforpligtet isolationsniveau inkluderer nogle subtile adfærd, som vi skal være opmærksomme på, før vi træffer det "informerede valg."
Tildelingsordrescanninger
Brug af læse-uforpligtet isolation tages af SQL Server som et signal om, at vi er parate til at acceptere de uoverensstemmelser, der kan opstå som et resultat af en allokeringsordret scanning.
Normalt kan lagermaskinen kun vælge en allokeringsordret scanning, hvis de underliggende data garanteret ikke ændres under scanningen (fordi f.eks. databasen er skrivebeskyttet, eller et tip til tabellåsning blev angivet). Men når læst uforpligtet isolation er i brug, kan lagermotoren stadig vælge en allokeringsordret scanning, selv hvor de underliggende data kan blive ændret af samtidige transaktioner.
Under disse omstændigheder kan den allokeringsordrede scanning gå glip af nogle forpligtede data fuldstændigt eller støde på andre forpligtede data mere end én gang. Der lægges vægt på manglende eller dobbelttælling af forpligtede data (ikke læsning af uforpligtende data), så der er ikke tale om "dirty reads" som sådan. Denne designbeslutning (for at tillade allokeringsordrede scanninger under læst uforpligtende isolation) anses af nogle mennesker for at være ret kontroversiel.
Som en advarsel skal jeg være klar over, at den mere generelle risiko for manglende eller dobbelttælling af forpligtede rækker ikke er begrænset til at læse uforpligtende isolation. Det er bestemt muligt at se lignende effekter under låsende læsning committed og repeterbar læsning (som vi så tidligere), men dette sker via en anden mekanisme. Manglende forpligtede rækker eller støder på dem flere gange på grund af en allokeringsbestemt scanning over skiftende data er specifik for at bruge læst uforpligtende isolation.
Læsning af "korrupte" data
Resultater, der ser ud til at trodse logikken (og endda kontrollere begrænsninger!) er mulige under låsende læseforpligtet isolation (igen, se denne artikel af Craig Freedman for nogle eksempler). For at opsummere, så er pointen, at låsning af læst committed kan se committed data fra forskellige tidspunkter – selv for en enkelt række, hvis forespørgselsplanen f.eks. bruger teknikker som indekskryds.
Disse resultater kan være uventede, men de er fuldstændig i overensstemmelse med garantien for kun at læse forpligtede data. Der er bare ingen komme væk fra det faktum, at højere datakonsistensgarantier kræver højere isolationsniveauer.
Disse eksempler kan endda være ret chokerende, hvis du ikke har set dem før. De samme resultater er selvfølgelig mulige under læst uforpligtende isolation, men at tillade beskidte aflæsninger tilføjer en ekstra dimension:resultaterne kan omfatte forpligtede og ikke-forpligtede data fra forskellige tidspunkter, selv for den samme række.
Hvis man går videre, er det endda muligt for en læst ikke-forpligtet transaktion at læse en enkelt kolonneværdi i en blandet tilstand af engagerede og ikke-forpligtede data. Dette kan forekomme, når du læser en LOB-værdi (f.eks. xml eller en af 'max'-typerne), hvis værdien er gemt på tværs af flere datasider. En ikke-forpligtet læsning kan støde på forpligtede eller ikke-forpligtede data fra forskellige tidspunkter på forskellige sider, hvilket resulterer i en endelig enkeltkolonneværdi, der er en blanding af værdier!
For at tage et eksempel, overvej en enkelt varchar(max) kolonne, der oprindeligt indeholder 10.000 'x' tegn. En samtidig transaktion opdaterer denne værdi til 10.000 'y'-tegn. En læst ikke-forpligtet transaktion kan læse 'x'-tegn fra én side i LOB'en og 'y'-tegn fra en anden, hvilket resulterer i en endelig læst værdi, der indeholder en blanding af 'x'- og 'y'-tegn. Det er svært at argumentere for, at dette ikke repræsenterer læsning af "korrupte" data.
Demo
Opret en klynget tabel med en enkelt række LOB-data:
CREATE TABLE dbo.Test ( RowID integer PRIMARY KEY, LOB varchar(max) NOT NULL, ); INSERT dbo.Test (RowID, LOB) VALUES (1, REPLICATE(CONVERT(varchar(max), 'X'), 16100));
I en separat session skal du køre følgende script for at læse LOB-værdien ved læst uforpligtet isolation:
-- Run this in session 2 SET NOCOUNT ON; DECLARE @ValueRead varchar(max) = '', @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100), @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100); WHILE 1 = 1 BEGIN SELECT @ValueRead = T.LOB FROM dbo.Test AS T WITH (READUNCOMMITTED) WHERE T.RowID = 1; IF @ValueRead NOT IN (@AllXs, @AllYs) BEGIN PRINT LEFT(@ValueRead, 8000); PRINT RIGHT(@ValueRead, 8000); BREAK; END END;
I den første session skal du køre dette script for at skrive vekslende værdier til LOB-kolonnen:
-- Run this in session 1 SET NOCOUNT ON; DECLARE @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100), @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100); WHILE 1 = 1 BEGIN UPDATE dbo.Test SET LOB = @AllYs WHERE RowID = 1; UPDATE dbo.Test SET LOB = @AllXs WHERE RowID = 1; END;
Efter kort tid vil scriptet i session to afsluttes efter at have læst en blandet tilstand for LOB-værdien, for eksempel:
Dette særlige problem er begrænset til læsninger af LOB-kolonneværdier, der er spredt over flere sider, ikke på grund af garantier fra isolationsniveauet, men fordi SQL Server tilfældigvis bruger sideniveaulåse for at sikre fysisk integritet. En bivirkning af denne implementeringsdetalje er, at den forhindrer sådanne "korrupte" datalæsninger, hvis dataene for en enkelt læsehandling tilfældigvis findes på en enkelt side.
Afhængigt af hvilken version af SQL Server du har, vil du, hvis "blandet tilstand"-data læses for en xml-kolonne, enten få en fejl som følge af det muligvis misdannede xml-resultat, ingen fejl overhovedet eller den ikke-forpligtede-specifikke fejl 601 , "kunne ikke fortsætte scanningen med NOLOCK på grund af databevægelse." Læsning af blandet tilstandsdata for andre LOB-typer resulterer generelt ikke i en fejlmeddelelse; den forbrugende applikation eller forespørgsel har ingen måde at vide, at den lige har oplevet den værste form for beskidt læsning. For at fuldføre analysen rapporteres en ikke-LOB blandet række, der er læst som et resultat af et indekskryds, aldrig som en fejl.
Budskabet her er, at hvis du bruger læst uforpligtende isolation, accepterer du, at dirty reads inkluderer muligheden for at læse "korrupte" blandede tilstande LOB-værdier.
NOLOCK-tip
Jeg formoder, at ingen diskussion om niveauet for læst uengageret isolation ville være komplet uden i det mindste at nævne dette (meget overbrugte og misforståede) tabeltip. Selve tippet er kun et synonym for READUNCOMMITTED-tabeltipet. Det udfører nøjagtig den samme funktion:det objekt, som det er anvendt på, tilgås ved hjælp af læst uforpligtende isolationssemantik (selvom der er en undtagelse).
Hvad angår navnet "NOLOCK", betyder det blot, at ingen delte låse tages ved læsning af data . Andre låse (skemastabilitet, eksklusive låse til dataændring og så videre) tages stadig som normalt.
Generelt set bør NOLOCK-tip være omtrent lige så almindelige som andre tip til isolationsniveauer pr. objekt som SERIALIZABLE og READCOMMITTEDLOCK. Det vil sige:slet ikke særlig almindeligt, og bruges kun, hvor der ikke er noget godt alternativ, et veldefineret formål med det og en komplet forståelse for konsekvenserne.
Et eksempel på en lovlig brug af NOLOCK (eller READUNCOMMITTED) er, når du får adgang til DMV'er eller andre systemvisninger, hvor et højere isolationsniveau kan forårsage uønsket strid om ikke-brugerdatastrukturer. Et andet edge-case eksempel kan være, hvor en forespørgsel skal have adgang til en betydelig del af en stor tabel, som med garanti aldrig vil opleve dataændringer, mens den antydede forespørgsel udføres. Der skal være en god grund til ikke at bruge snapshot- eller læse-committed snapshot-isolation i stedet, og de forventede ydelsesforøgelser skal testes, valideres og sammenlignes med f.eks. ved hjælp af et enkelt delt tabellåstip.
Den mindst ønskværdige brug af NOLOCK er den, der desværre er mest almindelig:at anvende den på hvert objekt i en forespørgsel som en slags go-faster magic switch. Med den bedste vilje i verden er der bare ingen bedre måde at få SQL Server-kode til at se decideret amatøragtig ud. Hvis du legitimt har brug for læst uforpligtende isolation til en forespørgsel, kodeblok eller modul, er det sandsynligvis bedre at indstille sessionsisolationsniveauet korrekt og give kommentarer til at retfærdiggøre handlingen.
Sidste tanker
Read uncommitted er et legitimt valg for transaktionsisoleringsniveau, men det skal være et informeret valg. Som en påmindelse, her er nogle af de samtidighedsfænomener, der er mulige under SQL Server-standardlåsen, læs committed isolation:
- Mangler tidligere forpligtede rækker
- Forpligtede rækker er stødt på flere gange
- Forskellige forpligtede versioner af den samme række stødt på i en enkelt erklæring/forespørgselsplan
- Forpligtede data fra forskellige tidspunkter i samme række (men forskellige kolonner)
- Forpligtede datalæsninger, der ser ud til at modsige aktiverede og kontrollerede begrænsninger
Afhængigt af dit synspunkt kan det være en ganske chokerende liste over mulige uoverensstemmelser for standardisolationsniveauet. Til den liste, læs uforpligtende isolation tilføjer:
- Beskidte læsninger (støder på data, der endnu ikke er, og måske aldrig bliver, begået)
- Rækker, der indeholder en blanding af forpligtede og ikke-forpligtede data
- Ubesvarede/duplikerede rækker på grund af allokeringsordnede scanninger
- Blandet tilstand ("korrupt") individuelle (enkelt kolonne) LOB-værdier
- Fejl 601 – "kunne ikke fortsætte scanningen med NOLOCK på grund af databevægelse" (eksempel).
Hvis dine primære transaktionsbekymringer handler om bivirkningerne ved at låse læseforpligtet isolation – blokering, låsning af overhead, reduceret samtidighed på grund af låseeskalering og så videre – er du måske bedre tjent med et rækkeversionsisoleringsniveau som læst committed snapshot-isolation (RCSI) eller snapshot isolation (SI). Disse er dog ikke gratis, og især opdateringer under RCSI har en kontraintuitiv adfærd.
For scenarier, der kræver de allerhøjeste niveauer af konsistensgarantier, er serialisering det eneste sikre valg. For ydeevnekritiske operationer på skrivebeskyttede data (f.eks. store databaser, der effektivt er skrivebeskyttede mellem ETL-vinduer), kan det også være et godt valg at indstille databasen til READ_ONLY (delte låse tages ikke, når databasen er skrivebeskyttet, og der er ingen risiko for inkonsekvens).
Der vil også være et relativt lille antal applikationer, hvor læst uforpligtende isolation er det rigtige valg. Disse applikationer skal være tilfredse med omtrentlige resultater og muligheden for lejlighedsvis inkonsistente, tilsyneladende ugyldige (med hensyn til begrænsninger) eller "formentlig korrupte" data. Hvis data ændres relativt sjældent, er risikoen for disse uoverensstemmelser også tilsvarende lavere.
[ Se indekset for hele serien ]