sql >> Database teknologi >  >> RDS >> Database

Det læste forpligtede isolationsniveau

[ Se indekset for hele serien ]

Læst engageret er den næst svageste af de fire isolationsniveauer defineret af SQL-standarden. Ikke desto mindre er det standardisolationsniveauet for mange databasemotorer, inklusive SQL Server. Dette indlæg i en serie om isolationsniveauer og transaktioners ACID-egenskaber ser på de logiske og fysiske garantier, der faktisk gives af læst committed isolation.

Logiske garantier

SQL-standarden kræver, at en transaktion, der kører under read committed isolation, kun læser committed data. Det udtrykker dette krav ved at forbyde samtidighedsfænomenet kendt som en dirty read. En beskidt læsning opstår, hvor en transaktion læser data, der er skrevet af en anden transaktion, før den anden transaktion fuldføres. En anden måde at udtrykke dette på er at sige, at en beskidt læsning opstår, når en transaktion læser ikke-forpligtede data.

Standarden nævner også, at en transaktion, der kører ved læseforpligtet isolation, kan støde på samtidighedsfænomenerne kendt som ikke-gentagelige læsninger og fantomer . Selvom mange bøger forklarer disse fænomener i form af, at en transaktion kan se ændrede eller nye dataelementer, hvis data efterfølgende genlæses, kan denne forklaring forstærke misforståelsen at samtidighedsfænomener kun kan forekomme inden for en eksplicit transaktion, der indeholder flere udsagn. Sådan er det ikke. En enkelt erklæring uden en eksplicit transaktion er lige så sårbar over for de ikke-gentagelige læse- og fantomfænomener, som vi snart vil se.

Det er stort set alt, hvad standarden har at sige om emnet læst engageret isolation. Ved første øjekast virker kun at læse forpligtede data som en ret god garanti for fornuftig adfærd, men som altid er djævelen i detaljen. Så snart du begynder at lede efter potentielle smuthuller i denne definition bliver det kun for nemt at finde tilfælde, hvor vores læste forpligtede transaktioner måske ikke giver de resultater, vi kunne forvente. Igen vil vi diskutere disse mere detaljeret om et øjeblik eller to.

Forskellige fysiske implementeringer

Der er mindst to ting, der betyder, at den observerede opførsel af det læseforpligtede isolationsniveau kan være ret anderledes på forskellige databasemotorer. For det første gør SQL-standardkravet om kun at læse forpligtede data ikke betyder nødvendigvis, at de forpligtede data læst af en transaktion vil være de senest forpligtede data.

En databasemotor har tilladelse til at læse en forpligtet version af en række fra hvor som helst tidligere , og stadig overholde SQL-standarddefinitionen. Adskillige populære databaseprodukter implementerer læseforpligtet isolation på denne måde. Forespørgselsresultater opnået under denne implementering af læseforpligtet isolation kan være vilkårligt forældede , sammenlignet med den aktuelle forpligtede tilstand af databasen. Vi vil dække dette emne, da det gælder for SQL Server i næste indlæg i serien.

Den anden ting, jeg vil henlede din opmærksomhed på, er, at SQL-standarddefinitionen ikke gør udelukke en bestemt implementering i at give yderligere samtidighedseffekt beskyttelse ud over at forhindre beskidte læsninger . Standarden specificerer kun, at dirty reads ikke er tilladt, den kræver ikke, at andre samtidighedsfænomener skal tillades på ethvert givet isolationsniveau.

For at være klar over dette andet punkt, kunne en standardkompatibel databasemotor implementere alle isolationsniveauer ved hjælp af serialiserbar adfærd, hvis den vælger det. Nogle større kommercielle databasemotorer giver også en implementering af read committed, der går langt ud over blot at forhindre beskidte læsninger (selvom ingen går så langt som at give fuldstændig isolation i ACID ordets betydning).

Derudover læs committed for flere populære produkter isolation er den laveste isolationsniveau tilgængeligt; deres implementeringer af læst uforpligtet isolation er nøjagtig det samme som læst begået. Dette er tilladt af standarden, men denne slags forskelle tilføjer kompleksitet til den i forvejen vanskelige opgave med at migrere kode fra en platform til en anden. Når man taler om adfærden på et isolationsniveau, er det normalt også vigtigt at specificere den særlige platform.

Så vidt jeg ved, er SQL Server unik blandt de store kommercielle databasemotorer ved at levere to implementeringer af det læseforpligtede isolationsniveau, hver med meget forskellig fysisk adfærd. Dette indlæg dækker det første af disse, låsning læst forpligtet.

SQL-serverlåsning Læs begået

Hvis databaseindstillingen READ_COMMITTED_SNAPSHOT er OFF , SQL Server bruger en låsning implementering af læse-forpligtet isolationsniveau, hvor delte låse tages for at forhindre en samtidig transaktion i samtidig at ændre dataene, fordi modifikation ville kræve en eksklusiv lås, som ikke er kompatibel med den delte lås.

Nøgleforskellen mellem SQL Server låsning af læst committed og låsning af gentagelig læsning (som også tager delte låse ved læsning af data) er, at read committed frigiver den delte lås så hurtigt som muligt , hvorimod gentagelig læsning holder disse låse til slutningen af ​​den omsluttende transaktion.

Når låsning af læst committed opnår låse ved rækkegranularitet, frigives den delte lås, der er taget på en række når en delt lås tages på næste række . Ved sidegranularitet frigives den delte sidelås, når den første række på næste side læses, og så videre. Medmindre et låsegranularitetstip er leveret sammen med forespørgslen, bestemmer databasemotoren, hvilket granularitetsniveau der skal startes med. Bemærk, at granularitetstip kun behandles som forslag af motoren, en mindre granulær lås end anmodet kan stadig tages i starten. Låse kan også blive eskaleret under udførelse fra række- eller sideniveau til partitions- eller tabelniveau afhængigt af systemkonfigurationen.

Det vigtige her er, at delte låse typisk kun holdes i meget kort tid mens erklæringen udføres. For at adressere en almindelig misforståelse eksplicit, ikke gør låsning af læsning begået hold delte låse til slutningen af ​​erklæringen.

Låsning af læst engageret adfærd

De kortsigtede delte låse, der bruges af SQL Server-låsning af læse-committet implementering, giver meget få af de garantier, der almindeligvis forventes af en databasetransaktion af T-SQL-programmører. Især en erklæring, der kører under låsning læst begået isolation:

  • Kan støde på den samme række flere gange;
  • Kan gå helt glip af nogle rækker; og
  • Gør det ikke give en tidspunkt-visning af dataene

Den liste virker måske mere som en beskrivelse af den mærkelige adfærd, du måske forbinder mere med brugen af ​​NOLOCK hints, men alle disse ting kan virkelig og sker, når du bruger låsende læse-forpligtet isolation.

Eksempel

Overvej den enkle opgave at tælle rækkerne i en tabel ved at bruge den åbenlyse enkelt-sætningsforespørgsel. Under låsning af læst forpligtet isolation med rækkelåsende granularitet, vil vores forespørgsel tage en delt lås på den første række, læse den, frigive den delte lås, gå videre til næste række, og så videre, indtil den når slutningen af ​​strukturen. læser. Af hensyn til dette eksempel, antag, at vores forespørgsel læser et indeks b-træ i stigende nøglerækkefølge (selvom det lige så godt kunne bruge en faldende rækkefølge eller en hvilken som helst anden strategi).

Da kun en enkelt række er aktielåst på ethvert givet tidspunkt, er det klart muligt for samtidige transaktioner at ændre de ulåste rækker i indekset, som vores forespørgsel gennemgår. Hvis disse samtidige ændringer ændrer indeksnøgleværdier, vil de få rækker til at flytte rundt i indeksstrukturen. Med den mulighed i tankerne illustrerer diagrammet nedenfor to problematiske scenarier, der kan opstå:

Den øverste pil viser en række, som vi allerede har talt, med dens indeksnøgle ændret samtidigt, så rækken bevæger sig foran den aktuelle scanningsposition i indekset, hvilket betyder, at rækken bliver talt to gange . Den anden pil viser en række, som vores scanning endnu ikke er stødt på, bevæger sig bag scanningspositionen, hvilket betyder, at rækken ikke tælles overhovedet.

Ikke et punkt-i-tidsvisning

Det forrige afsnit viste, hvordan låsning af læst begået kan gå glip af data fuldstændigt eller tælle det samme element flere gange (mere end to gange, hvis vi er uheldige). Det tredje punkt på listen over uventet adfærd erklærede, at låsning af læst begået heller ikke giver et punkt-i-tidsbillede af dataene.

Begrundelsen bag denne udtalelse burde nu være let at se. Vores tælleforespørgsel kunne for eksempel nemt læse data, der blev indsat ved samtidige transaktioner, efter at vores forespørgsel begyndte at udføre. Ligeledes kan data, som vores forespørgsel ser, blive ændret af samtidig aktivitet, efter vores forespørgsel starter, og før den afsluttes. Endelig kan data, vi har læst og talt, blive slettet ved en samtidig transaktion, før vores forespørgsel er fuldført.

Det er klart, at de data, der ses af en erklæring eller transaktion, der kører under låsende læse-committet isolation, svarer til ingen enkelt tilstand af databasen på et bestemt tidspunkt . De data, vi støder på, kan meget vel være fra en række forskellige tidspunkter, hvor den eneste fælles faktor er, at hvert element repræsenterede den seneste forpligtede værdi af disse data på det tidspunkt, de blev læst (selvom det godt kunne have ændret sig eller forsvundet siden).

Hvor alvorlige er disse problemer?

Det hele kan virke som en ret ulden tilstand, hvis du er vant til at tænke på dine enkeltudsagns-forespørgsler og eksplicitte transaktioner som logisk eksekverende øjeblikkeligt eller som at køre mod en enkelt forpligtet tidspunkt-tilstand i databasen, når du bruger standard SQL Server isolationsniveau. Det passer bestemt ikke godt med begrebet isolation i SYRE-forstand.

I betragtning af den tilsyneladende svaghed ved de garantier, der ydes ved at låse læseforpligtet isolation, begynder du måske at spekulere på, hvordan enhver af din produktions-T-SQL-kode nogensinde har fungeret korrekt! Selvfølgelig kan vi acceptere, at brug af et isolationsniveau under serialiserbart betyder, at vi opgiver fuld ACID-transaktionsisolering til gengæld for andre potentielle fordele, men hvor alvorlige kan vi forvente, at disse problemer er i praksis?

Manglende og dobbelttællede rækker

Disse to første problemer er i det væsentlige afhængige af samtidige aktivitetsændringer af nøgler i en indeksstruktur, som vi i øjeblikket scanner. Bemærk, at scanning inkluderer her den delvise områdescanningsdel af en indekssøgning , såvel som den velkendte ubegrænsede indeks- eller tabelscanning.

Hvis vi (interval) scanner en indeksstruktur, hvis nøgler ikke typisk modificeres af nogen samtidig aktivitet, burde disse to første problemer ikke være et stort praktisk problem. Det er dog svært at være sikker på dette, fordi forespørgselsplaner kan ændres til at bruge en anden adgangsmetode, og det nye søgte indeks kan inkorporere flygtige nøgler.

Vi skal også huske på, at mange produktionsforespørgsler kun behøver en ca. eller svar på nogle typer spørgsmål alligevel. Det faktum, at nogle rækker mangler eller tælles dobbelt, betyder måske ikke meget i den bredere sammenhæng. På et system med mange samtidige ændringer kan det endda være svært at være sikker på, at resultatet var unøjagtige, da dataene ændres så ofte. I den slags situationer kan et nogenlunde korrekt svar være godt nok til dataforbrugerens formål.

Ingen punkt-i-tidsvisning

Det tredje spørgsmål (spørgsmålet om et såkaldt 'konsistent' tidspunkt på tidspunktet for dataene) kommer også ned til den samme slags overvejelser. Til rapporteringsformål, hvor uoverensstemmelser har tendens til at resultere i akavede spørgsmål fra dataforbrugerne, er et øjebliksbillede ofte at foretrække. I andre tilfælde kan den slags uoverensstemmelser, der opstår som følge af manglen på et punkt-i-tidsbillede af dataene, godt tåles.

Problematiske scenarier

Der er også masser af tilfælde, hvor de anførte bekymringer vil være vigtig. For eksempel, hvis du skriver kode, der håndhæver forretningsregler i T-SQL skal du være omhyggelig med at vælge et isolationsniveau (eller tage andre passende handlinger) for at garantere korrekthed. Mange forretningsregler kan håndhæves ved hjælp af fremmednøgler eller begrænsninger, hvor forviklingerne ved valg af isolationsniveau håndteres automatisk for dig af databasemotoren. Som en generel tommelfingerregel bruger det indbyggede sæt deklarativ integritet funktioner er at foretrække frem for at bygge dine egne regler i T-SQL.

Der er en anden bred klasse af forespørgsler, der ikke helt håndhæver en forretningsregel i sig selv , men som ikke desto mindre kan have uheldige konsekvenser, når de køres på standard låse-læs committed isolationsniveau. Disse scenarier er ikke altid så indlysende som de ofte citerede eksempler på at overføre penge mellem bankkonti eller sikre, at saldoen over et antal forbundne konti aldrig falder under nul. Overvej f.eks. følgende forespørgsel, der identificerer forfaldne fakturaer som input til en proces, der udsender strengt formulerede rykkerbreve:

INSERT dbo.OverdueInvoices
SELECT I.InvoiceNumber
FROM dbo.Invoices AS INV
WHERE INV.TotalDue >
(
    SELECT SUM(P.Amount)
    FROM dbo.Payments AS P
    WHERE P.InvoiceNumber = I.InvoiceNumber
);

Det er klart, at vi ikke ønsker at sende et brev til en person, der fuldt ud havde betalt deres faktura i rater, simpelthen fordi samtidig databaseaktivitet på det tidspunkt, vores forespørgsel kørte, betød, at vi beregnede et forkert beløb af modtagne betalinger. Reelle forespørgsler på rigtige produktionssystemer er ofte meget mere komplekse end det simple eksempel ovenfor, selvfølgelig.

For at afslutte for i dag, tag et kig på følgende forespørgsel og se, om du kan se, hvor mange muligheder der er for, at noget utilsigtet kan ske, hvis flere sådanne forespørgsler køres samtidigt på det låsende læse-committede isolationsniveau (måske mens andre ikke-relaterede transaktioner ændrer også tabellen Sager):

-- Allocate the oldest unallocated case ID to 
-- the current case worker, while ensuring
-- the worker never has more than three
-- active cases at once.
UPDATE dbo.Cases
SET WorkerID = @WorkerID
WHERE 
    CaseID = 
    (
        -- Find the oldest unallocated case ID
        SELECT TOP (1)
            C2.CaseID
        FROM dbo.Cases AS C2
        WHERE 
            C2.WorkerID IS NULL
        ORDER BY 
            C2.DateCreated DESC
    )
    AND
    (
        SELECT COUNT_BIG(*) 
        FROM dbo.Cases AS C3
        WHERE C3.WorkerID = @WorkerID
    ) < 3;

Når du først begynder at lede efter alle de små måder, hvorpå en forespørgsel kan gå galt på dette isolationsniveau, kan det være svært at stoppe. Husk de advarsler, der er nævnt tidligere, omkring det reelle behov for fuldstændigt isolerede og præcise resultater på tidspunktet. Det er helt fint at have forespørgsler, der giver gode nok resultater, så længe du er opmærksom på de afvejninger, du foretager dig ved at bruge read committed.

Næste gang

Den næste del i denne serie ser på den anden fysiske implementering af read committed isolation tilgængelig i SQL Server, read committed snapshot isolation.

[ Se indekset for hele serien ]


  1. MySQL versus MariaDB

  2. Sådan får du maks. og min. værdier fra en tabel ved hjælp af aggregeret funktion - SQL Server / TSQL vejledning del 129

  3. Beregning af kumulativ sum i PostgreSQL

  4. Hvad er SQL Server RAISERROR?