Enhver programmør vil fortælle dig, at det kan være svært at skrive sikker flertrådskode. Det kræver stor omhu og en god forståelse af de involverede tekniske problemer. Som databaseperson tror du måske, at denne slags vanskeligheder og komplikationer ikke gælder, når du skriver T-SQL. Så det kan komme som lidt af et chok at indse, at T-SQL-kode også er sårbar over for den slags raceforhold og andre dataintegritetsrisici, der oftest er forbundet med multi-threaded programmering. Dette er sandt, uanset om vi taler om en enkelt T-SQL-sætning eller en gruppe af udsagn indesluttet i en eksplicit transaktion.
Kernen i problemet er det faktum, at databasesystemer tillader flere transaktioner at udføre på samme tid. Dette er en velkendt (og meget ønskværdig) situation, men en stor del af T-SQL-produktionskoden antager stadig stille og roligt, at de underliggende data ikke ændres under udførelsen af en transaktion eller en enkelt DML-sætning som SELECT
, INSERT
, UPDATE
, DELETE
, eller MERGE
.
Selv hvor kodeforfatteren er opmærksom på de mulige virkninger af samtidige dataændringer, antages brugen af eksplicitte transaktioner for ofte at give mere beskyttelse, end der faktisk er berettiget. Disse antagelser og misforståelser kan være subtile og er bestemt i stand til at vildlede selv erfarne databaser.
Nu er der tilfælde, hvor disse spørgsmål ikke betyder meget i praktisk forstand. For eksempel kan databasen være skrivebeskyttet, eller der kan være en anden ægte garanti at ingen andre vil ændre de underliggende data, mens vi arbejder med dem. Ligeledes kræver den pågældende operation muligvis ikke resultater, der er præcis korrekt; vores dataforbrugere kan være helt tilfredse med et omtrentligt resultat (selv et, der ikke repræsenterer databasens forpligtede tilstand nogen tidspunkt).
Samtidighedsproblemer
Spørgsmålet om interferens mellem opgaver, der udføres samtidig, er et velkendt problem for applikationsudviklere, der arbejder i programmeringssprog som C# eller Java. Løsningerne er mange og varierede, men involverer generelt brug af atomare operationer eller opnå en gensidigt eksklusiv ressource (såsom en lås ), mens en følsom operation er i gang. Hvor de rette forholdsregler ikke tages, er de sandsynlige resultater beskadigede data, en fejl eller måske endda et fuldstændigt nedbrud.
Mange af de samme begreber (f.eks. atomoperationer og låse) findes i databaseverdenen, men desværre har de ofte afgørende betydningsforskelle . De fleste databasefolk er opmærksomme på ACID-egenskaberne ved databasetransaktioner, hvor A'et står for atomic . SQL Server bruger også låse (og andre gensidige udelukkelsesanordninger internt). Ingen af disse udtryk betyder helt, hvad en erfaren C#- eller Java-programmør med rimelighed ville forvente, og mange databaseprofessionelle har også en forvirret forståelse af disse emner (hvilket en hurtig søgning ved hjælp af din yndlingssøgemaskine vil bevidne).
For at gentage, nogle gange vil disse spørgsmål ikke være et praktisk problem. Hvis du skriver en forespørgsel for at tælle antallet af aktive ordrer i et databasesystem, hvor vigtigt er det, hvis optællingen er en smule off? Eller hvis det afspejler databasens tilstand på et andet tidspunkt?
Det er almindeligt, at rigtige systemer foretager en afvejning mellem samtidighed og konsistens (selvom designeren ikke var bevidst om det på det tidspunkt – informeret afvejninger er måske et sjældnere dyr). Rigtige systemer fungerer ofte godt nok , med eventuelle anomalier kortvarige eller betragtes som uvigtige. En bruger, der ser en inkonsekvent tilstand på en webside, vil ofte løse problemet ved at opdatere siden. Hvis problemet rapporteres, vil det højst sandsynligt blive lukket som ikke reproducerbart. Jeg siger ikke, at dette er en ønskværdig tilstand, bare jeg erkender, at det sker.
Ikke desto mindre er det enormt nyttigt at forstå samtidighedsproblemer på et grundlæggende niveau. At være opmærksom på dem gør det muligt for os at skrive korrekt (eller informeret). korrekt-nok) T-SQL efter omstændighederne. Endnu vigtigere, det giver os mulighed for at undgå at skrive T-SQL, der kunne kompromittere den logiske integritet af vores data.
Men SQL Server giver ACID-garantier!
Ja, det gør det, men de er ikke altid, hvad du ville forvente, og de beskytter ikke alt. Oftere end ikke læser mennesker langt mere i ACID end det er berettiget.
De hyppigst misforståede komponenter i ACID-akronymet er ordene Atomic, Consistent og Isolated – dem kommer vi til om et øjeblik. Den anden, Holdbar , er intuitiv nok, så længe du husker, at den kun gælder for vedvarende (kan gendannes) bruger data.
Med alt det sagt, begynder SQL Server 2014 at sløre grænserne for Durable-egenskaben noget med introduktionen af generel forsinket holdbarhed og OLTP-skema-only holdbarhed i hukommelsen. Jeg nævner dem kun for fuldstændighedens skyld, vi vil ikke diskutere disse nye funktioner yderligere. Lad os gå videre til de mere problematiske ACID-egenskaber:
Atomegenskaben
Mange programmeringssprog giver atomiske operationer der kan bruges til at beskytte mod løbsforhold og andre uønskede samtidighedseffekter, hvor flere eksekveringstråde kan få adgang til eller ændre delte datastrukturer. For applikationsudvikleren kommer en atomoperation med en eksplicit garanti for fuldstændig isolation fra virkningerne af anden samtidig behandling i et flertrådet program.
En analog situation opstår i databaseverdenen, hvor flere T-SQL-forespørgsler samtidig får adgang til og modificerer delte data (dvs. databasen) fra forskellige tråde. Bemærk, at vi ikke taler om parallelle forespørgsler her; almindelige enkelttrådede forespørgsler er rutinemæssigt planlagt til at køre samtidigt i SQL Server på separate arbejdstråde.
Desværre er den atomare egenskab af SQL-transaktioner garanterer kun, at dataændringer udført inden for en transaktion lykkes eller mislykkes som en enhed . Ikke mere end det. Der er bestemt ingen garanti for fuldstændig isolation fra virkningerne af anden samtidig behandling. Bemærk også i forbifarten, at atomtransaktionsegenskaben ikke siger noget om nogen garantier for læsning data.
Enkelte erklæringer
Der er heller ikke noget særligt ved et enkelt udsagn i SQL Server. Hvor en eksplicit indeholdende transaktion (BEGIN TRAN...COMMIT TRAN
) ikke eksisterer, udføres en enkelt DML-sætning stadig inden for en autocommit-transaktion. De samme ACID-garantier gælder for en enkelt erklæring og de samme begrænsninger. Især kommer en enkelt erklæring uden særlige garantier for, at data ikke ændres, mens de er i gang.
Overvej følgende legetøjs AdventureWorks-forespørgsel:
SELECT TH.TransactionID, TH.ProductID, TH.ReferenceOrderID, TH.ReferenceOrderLineID, TH.TransactionDate, TH.TransactionType, TH.Quantity, TH.ActualCost FROM Production.TransactionHistory AS TH WHERE TH.ReferenceOrderID = ( SELECT TOP (1) TH2.ReferenceOrderID FROM Production.TransactionHistory AS TH2 WHERE TH2.TransactionType = N'P' ORDER BY TH2.Quantity DESC, TH2.ReferenceOrderID ASC );
Forespørgslen er beregnet til at vise information om ordren, der rangeres først efter antal. Udførelsesplanen er som følger:
De vigtigste operationer i denne plan er:
- Scan tabellen for at finde rækker med den påkrævede transaktionstype
- Find det ordre-id, der sorterer højest i henhold til specifikationen i underforespørgslen
- Find rækkerne (i samme tabel) med det valgte ordre-id ved hjælp af et ikke-klynget indeks
- Slå de resterende kolonnedata op ved hjælp af det klyngede indeks
Forestil dig nu, at en samtidig bruger ændrer ordre 495, ændrer dens transaktionstype fra P til W og begår denne ændring til databasen. Som heldet ville have det, går denne ændring igennem, mens vores forespørgsel udfører sorteringsoperationen (trin 2).
Når sorteringen er fuldført, finder indekssøgningen i trin 3 rækkerne med det valgte ordre-id (som tilfældigvis er 495), og nøgleopslaget i trin 4 henter de resterende kolonner fra basistabellen (hvor transaktionstypen nu er W) .
Denne rækkefølge af hændelser betyder, at vores forespørgsel producerer et tilsyneladende umuligt resultat:
I stedet for at finde ordrer med transaktionstype P som den angivne forespørgsel, viser resultaterne transaktionstype W.
Grundårsagen er klar:vores forespørgsel antog implicit, at dataene ikke kunne ændre sig, mens vores enkeltudsagnsforespørgsel var i gang. Mulighedsvinduet i dette tilfælde var relativt stort på grund af blokeringstypen, men den samme slags racetilstand kan generelt set forekomme på ethvert trin af forespørgselsudførelsen. Naturligvis er risikoen normalt højere med øgede niveauer af samtidige ændringer, større tabeller, og hvor blokerende operatører optræder i forespørgselsplanen.
En anden vedvarende myte i det samme generelle område er, at MERGE
er at foretrække frem for separat INSERT
, UPDATE
og DELETE
sætninger, fordi enkeltsætningen MERGE
er atomare. Det er selvfølgelig noget sludder. Vi vil vende tilbage til denne form for ræsonnement senere i serien.
Den generelle besked på dette tidspunkt er, at medmindre der tages eksplicitte trin for at sikre andet, kan datarækker og indeksposter ændre sig, flytte position eller forsvinde helt når som helst under udførelsesprocessen. Et mentalt billede af konstante og tilfældige ændringer i databasen er godt at have i tankerne, mens du skriver T-SQL-forespørgsler.
Konsistensegenskaben
Det andet ord fra ACID akronymet har også en række mulige fortolkninger. I en SQL Server-database betyder Konsistens kun at en transaktion forlader databasen i en tilstand, der ikke overtræder nogen aktive begrænsninger. Det er vigtigt fuldt ud at forstå, hvor begrænset denne erklæring er:De eneste ACID-garantier for dataintegritet og logisk konsistens er dem, der leveres af aktive begrænsninger.
SQL Server giver et begrænset udvalg af begrænsninger for at håndhæve logisk integritet, inklusive PRIMARY KEY
, FOREIGN KEY
, CHECK
, UNIQUE
, og NOT NULL
. Disse er alle garanteret opfyldt på det tidspunkt, hvor en transaktion forpligter sig. Derudover garanterer SQL Server det fysiske databasens integritet til enhver tid, selvfølgelig.
De indbyggede begrænsninger er ikke altid tilstrækkelige til at håndhæve alle de forretnings- og dataintegritetsregler, vi gerne vil have. Det er bestemt muligt at være kreativ med standardfaciliteterne, men disse bliver hurtigt komplekse og kan resultere i lagring af duplikerede data.
Som en konsekvens heraf indeholder de fleste rigtige databaser i det mindste nogle T-SQL-rutiner skrevet for at håndhæve yderligere regler, for eksempel i lagrede procedurer og triggere. Ansvaret for at sikre, at denne kode fungerer korrekt, ligger udelukkende hos forfatteren – Consistency-egenskaben giver ingen specifik beskyttelse.
For at understrege pointen skal pseudo-begrænsninger skrevet i T-SQL fungere korrekt, uanset hvilke samtidige ændringer der måtte forekomme. En applikationsudvikler kan beskytte en følsom handling som den med en låseerklæring. Det tætteste, T-SQL-programmører har til denne facilitet til at udsætte procedurer og triggerkode i fare, er den forholdsvis sjældent brugte sp_getapplock
system lagret procedure. Det betyder ikke, at det er den eneste eller endda foretrukne mulighed, bare at den eksisterer og kan være det rigtige valg under nogle omstændigheder.
Isolationsejendommen
Dette er let den oftest misforståede af ACID-transaktionsegenskaberne.
I princippet en helt isoleret transaktion udføres som den eneste opgave, der udføres mod databasen i løbet af dens levetid. Andre transaktioner kan først starte, når den aktuelle transaktion er fuldstændig afsluttet (dvs. forpligtet eller rullet tilbage). Udført på denne måde ville en transaktion virkelig være en atomoperation , i den strenge forstand, som en ikke-databaseperson ville tilskrive sætningen.
I praksis opererer databasetransaktioner i stedet med en grad af isolation specificeret af det aktuelt effektive transaktionsisolationsniveau (som gælder lige så for selvstændige udsagn, husk). Dette kompromis (graden af isolation) er den praktiske konsekvens af de tidligere nævnte afvejninger mellem samtidighed og korrekthed. Et system, der bogstaveligt talt behandlede transaktioner én for én, uden overlap i tid, ville give fuldstændig isolation, men den samlede systemgennemstrømning ville sandsynligvis være dårlig.
Næste gang
Den næste del i denne serie vil fortsætte undersøgelsen af samtidighedsproblemer, ACID-egenskaber og transaktionsisolering med et detaljeret kig på det serialiserbare isolationsniveau, endnu et eksempel på noget, der måske ikke betyder, hvad du tror, det gør.
[ Se indekset for hele serien ]