Låsing, blokkering og deadlocks i SQL Server
«Transaction (Process ID 72) was deadlocked on lock resources with another process and has been chosen as the deadlock victim.» Får du denne feilmeldingen, eller henger applikasjonen i perioder uten at serveren har mye å gjøre, er det sannsynligvis låsing som er årsaken. Denne guiden forklarer hvordan SQL Server bruker låser, hva forskjellen på blokkering og deadlock er, hvordan du finner synderen, og hvordan isolasjonsnivåer og radversjonering kan fjerne det meste av problemet.
- Blokkering er normalt og kortvarig: én transaksjon venter på at en annen slipper en lås.
- Deadlock er når to transaksjoner venter på hverandre. SQL Server avbryter den ene automatisk.
- Det viktigste tiltaket er korte transaksjoner og gode indekser.
- Read Committed Snapshot Isolation (RCSI) gjør at lesere ikke blokkerer skrivere og omvendt.
NOLOCKer ikke en løsning – den gir feil data.
Hvorfor låser SQL Server?
En database skal gi samme resultat som om transaksjonene kjørte én etter én, selv når hundrevis kjører samtidig. Det er I-en i ACID: isolation. SQL Server løser det i utgangspunktet med låser:
- Delt lås (S): Tas ved lesing. Mange kan holde delt lås på samme rad samtidig.
- Eksklusiv lås (X): Tas ved endring. Ingen andre kan holde noen lås på samme ressurs.
- Oppdateringslås (U): Tas når SQL Server leser en rad den kanskje skal endre. Hindrer en vanlig type deadlock.
- Intensjonslåser (IS, IX): Tas på side og tabell for å markere at det finnes låser lenger ned. Gjør det raskt å sjekke om hele tabellen kan låses.
Låser tas på rad-, side- eller tabellnivå. Holder én transaksjon mer enn ca. 5 000 låser på samme objekt, kan SQL Server eskalere til én tabellås for å spare minne – og da blokkeres alle andre som vil bruke tabellen. Store UPDATE- og DELETE-operasjoner bør derfor kjøres i mindre bolker.
Blokkering
Blokkering oppstår når en transaksjon trenger en lås som en annen holder. Den venter til låsen slippes – som regel ved COMMIT eller ROLLBACK. Det er helt normalt og varer vanligvis millisekunder. Det blir et problem når en transaksjon holder låser lenge, fordi:
- transaksjonen gjør mye arbeid (store oppdateringer uten indeks, slik at mange rader leses og låses),
- applikasjonen åpner en transaksjon og så venter på noe annet – en bruker, en fil, et eksternt API,
- noen har startet
BEGIN TRANi SSMS og glemt å avslutte, - applikasjonen glemmer å lukke tilkoblingen eller transaksjonen etter en feil.
Finn hvem som blokkerer
SELECT r.session_id AS Venter,
r.blocking_session_id AS BlokkertAv,
r.wait_type,
r.wait_time / 1000 AS SekunderVentet,
DB_NAME(r.database_id) AS DatabaseNavn,
t.text AS VentendeSpørring
FROM sys.dm_exec_requests AS r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
WHERE r.blocking_session_id <> 0;
-- Hva gjør den som blokkerer? (den kan være inaktiv med en åpen transaksjon)
SELECT s.session_id, s.login_name, s.host_name, s.program_name,
s.status, s.open_transaction_count, s.last_request_end_time
FROM sys.dm_exec_sessions AS s
WHERE s.session_id = 72;
Den som står øverst i kjeden – blokkerer andre, men ikke selv er blokkert – er «hodet». Er den sleeping med open_transaction_count større enn 0, venter SQL Server på at klienten skal gjøre ferdig transaksjonen sin. Det er applikasjonen eller brukeren som må fikse det. Som nødløsning kan sesjonen avsluttes med KILL 72, men da rulles transaksjonen tilbake, noe som kan ta like lang tid som den har kjørt.
sp_WhoIsActive med @find_block_leaders = 1 gir en mer lesbar oversikt over blokkeringskjeder.
Logg blokkering automatisk
Blokkering skjer gjerne når ingen ser på. Med blocked process threshold lager SQL Server en rapport for alle som har ventet lenger enn et gitt antall sekunder, som du kan fange opp med Extended Events:
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'blocked process threshold (s)', 15;
RECONFIGURE;
CREATE EVENT SESSION Blokkering ON SERVER
ADD EVENT sqlserver.blocked_process_report
ADD TARGET package0.event_file (SET filename = N'Blokkering')
WITH (STARTUP_STATE = ON);
ALTER EVENT SESSION Blokkering ON SERVER STATE = START;
Deadlocks
En deadlock er en vranglås: Transaksjon A holder lås på rad 1 og vil ha rad 2. Transaksjon B holder lås på rad 2 og vil ha rad 1. Ingen av dem kan noen gang bli ferdige. SQL Server oppdager dette i løpet av sekunder, velger et offer (som regel den transaksjonen som er billigst å rulle tilbake), og avbryter den med feil 1205. Den andre fortsetter.
Deadlocks er altså ikke en «heng» – de løses automatisk. Problemet er at offeret får en feil, og hvis applikasjonen ikke håndterer den, mister brukeren arbeidet sitt.
Finn deadlockene
SQL Server lagrer deadlockgrafer i den innebygde Extended Events-sesjonen system_health, så du trenger ikke slå på noe for å finne de siste:
SELECT x.value('(@timestamp)[1]', 'datetime2') AS TidspunktUtc,
x.query('(data/value/deadlock)[1]') AS DeadlockGraf
FROM (
SELECT CAST(event_data AS xml) AS EventXml
FROM sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL)
WHERE object_name = 'xml_deadlock_report'
) AS e
CROSS APPLY e.EventXml.nodes('/event') AS t(x)
ORDER BY TidspunktUtc DESC;
Lagre XML-en som en .xdl-fil og åpne den i SSMS for å se grafen: hvilke prosesser, hvilke spørringer, hvilke ressurser og hvilke låser som var involvert.
Forebygg deadlocks
- Gå gjennom tabellene i samme rekkefølge i alle transaksjoner. Oppdaterer alle først Ordre og så Ordrelinje, kan de ikke låse hverandre i kryss.
- Hold transaksjonene korte. Gjør beregninger, valideringer og eksterne kall før transaksjonen starter.
- Indekser riktig. Uten indeks må en
UPDATE ... WHERE KundeId = 5lese – og låse – langt flere rader enn den endrer. Mange deadlocks forsvinner med en passende indeks. - Bruk radversjonering (RCSI), slik at lesere ikke tar delte låser.
- Håndter feil 1205 i applikasjonen med et nytt forsøk. Deadlocks kan sjelden fjernes helt i et travelt system, så koden bør tåle dem.
Isolasjonsnivåer
Isolasjonsnivået bestemmer hvor mye en transaksjon skjermes fra andres endringer – og dermed hvor mye den låser.
| Nivå | Skitne lesinger | Ikke-repeterbare lesinger | Fantomrader | Kommentar |
|---|---|---|---|---|
| Read Uncommitted (NOLOCK) | Ja | Ja | Ja | Ingen delte låser. Kan også gi doble og manglende rader. |
| Read Committed | Nei | Ja | Ja | Standard. Lesere og skrivere blokkerer hverandre. |
| Read Committed Snapshot (RCSI) | Nei | Ja | Ja | Lesere ser siste bekreftede versjon og blokkeres ikke. |
| Repeatable Read | Nei | Nei | Ja | Holder delte låser til transaksjonen er ferdig. |
| Snapshot | Nei | Nei | Nei | Konsistent øyeblikksbilde fra transaksjonens start. Oppdateringskonflikter gir feil. |
| Serializable | Nei | Nei | Nei | Strengest. Låser også intervaller. Mest blokkering. |
Read Committed Snapshot Isolation
Med RCSI tar ikke lesere delte låser. I stedet lagrer SQL Server tidligere versjoner av endrede rader (i tempdb, eller i databasen selv når Accelerated Database Recovery er på), og en leser som møter en låst rad leser den siste bekreftede versjonen. Lesere blokkerer ikke skrivere, og skrivere blokkerer ikke lesere. Skrivere blokkerer fortsatt hverandre.
-- Krever at ingen andre er koblet til databasen mens kommandoen kjører
ALTER DATABASE Salg SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;
RCSI er standard i Azure SQL Database, og for de fleste OLTP-systemer et av de mest effektive tiltakene mot blokkering. Vær oppmerksom på:
- Økt bruk av tempdb (eller databasen) til versjonslageret, og 14 ekstra byte per endret rad.
- Kode som forutsetter at en lesing blokkerer – for eksempel «les saldo, sjekk, oppdater» uten
UPDLOCK– kan oppføre seg annerledes. Gjennomgå slike mønstre og test før du slår det på i produksjon.
Optimized locking i SQL Server 2025
SQL Server 2025 (og Azure SQL) har optimized locking, som bygger på Accelerated Database Recovery. Radlåser holdes bare mens hver rad endres i stedet for hele transaksjonen, og sammen med RCSI kan SQL Server sjekke om en rad oppfyller WHERE-betingelsen før den tar lås. Resultatet er langt færre låser, mindre eskalering og færre blokkeringer mellom samtidige oppdateringer.
Hvorfor NOLOCK ikke er løsningen
WITH (NOLOCK) er utbredt fordi det «fjerner blokkering». Men Read Uncommitted betyr ikke bare at du kan lese data som senere rulles tilbake. Fordi SQL Server kan lese sider i allokeringsrekkefølge mens andre flytter rader, kan du også få samme rad to ganger eller ikke i det hele tatt. For en rapport over omsetning eller lagerbeholdning er det feil tall, uten at noen merker det. Bruk RCSI eller Snapshot i stedet – det gir konsistente data uten blokkering.
Vanlige spørsmål
Hvordan bør applikasjonen håndtere deadlocks?
Fang feil 1205, vent noen få millisekunder (gjerne med litt tilfeldig variasjon), og prøv hele transaksjonen på nytt – typisk opptil tre ganger. I .NET kan Entity Framework Core gjøre dette automatisk med EnableRetryOnFailure().
Kan jeg velge hvem som blir deadlockoffer?
Ja, med SET DEADLOCK_PRIORITY LOW i sesjoner som tåler å bli avbrutt, for eksempel bakgrunnsjobber. Da velges de som offer foran interaktive brukere.
Er det farlig å bruke KILL?
Det avslutter sesjonen og ruller tilbake den åpne transaksjonen. Det er trygt for databasens konsistens, men endringene i transaksjonen går tapt, og tilbakerullingen kan ta lang tid. Finn ut hva sesjonen er før du avslutter den.