top of page

SQL deadlocks in SQL Server: herkennen, analyseren en oplossen

17 aug
10 minuten om te lezen

Een SQL deadlock is niet hetzelfde als een trage query of een vastgelopen scherm, en dat verschil bepaalt hoe je hem oplost. In dit artikel lees je hoe SQL Server deadlocks detecteert, hoe je de deadlock graph terugvindt en leest, en welke oplossingen werken — van indexen tot optimized locking in SQL Server 2025.

Wat is een SQL deadlock?

Een SQL deadlock is een situatie waarin twee of meer transacties elkaar permanent blokkeren: elke transactie houdt een lock vast die de ander nodig heeft. SQL Server detecteert deze cyclus, kiest één transactie als slachtoffer, draait die terug en geeft foutmelding 1205 terug aan de applicatie.

De deadlock monitor is een aparte thread die periodiek alle taken doorloopt. Volgens Microsoft Learn is het standaardinterval 5 seconden; zodra er deadlocks gevonden worden, zakt dat interval naar minimaal 100 milliseconden, en na een rustige periode loopt het weer terug naar 5 seconden.

Hoe kiest SQL Server het slachtoffer:

  • Heeft één sessie een lagere DEADLOCK_PRIORITY (instelbaar op LOW, NORMAL, HIGH of een geheel getal van -10 tot 10), dan wordt die het slachtoffer.

  • Zijn de prioriteiten gelijk, dan wint de transactie die het duurst is om terug te draaien. De kosten worden bepaald door het aantal geschreven transactielog-bytes.

  • Zijn prioriteit én kosten gelijk, dan kiest de engine willekeurig.

  • Een transactie die al aan het terugdraaien is, kan nooit slachtoffer worden.

Niet alleen locks kunnen deadlocken. Microsoft noemt daarnaast worker threads, geheugen (memory grants), resources rond parallelle queryuitvoering en MARS-resources zoals de session mutex en transaction mutex.

Deadlock of blokkering: waarom dit verschil ertoe doet

Dit is de meestgemaakte denkfout, en hij stuurt de hele analyse de verkeerde kant op.

Bij blocking wacht een transactie op een lock die een ander vasthoudt. Er is niets mis: de wachtende partij blokkeert de eigenaar niet. Standaard verlopen transacties in de database engine niet, tenzij LOCK_TIMEOUT is ingesteld. Blocking kan daardoor in theorie eindeloos duren.

Bij een deadlock is er een cyclus. Geen van beide kan verder, en zonder ingreep zouden ze allebei oneindig wachten. Daarom grijpt de engine in — en dat gaat bijna direct.

Praktisch onderscheid voor de servicedesk:

  • De gebruiker krijgt een foutmelding (vaak: “rerun the transaction”) → deadlock, foutcode 1205.

  • Het scherm hangt of loopt in een time-out → blocking, of een langlopende query.

Beide vragen om ander onderzoek. Bij blocking kijk je naar de blokkerende sessie en de duur van transacties; bij een deadlock naar de deadlock graph.

Wat veroorzaakt deadlocks in de praktijk?

De terugkerende oorzaken:

  • Scans in plaats van seeks. Een update die geen bruikbare index vindt, scant en lockt daardoor veel meer rijen dan hij nodig heeft. Microsoft noemt tabel- en indexscans expliciet als patroon dat blocking en deadlocks vergroot.

  • Verschillende volgorde van objecttoegang. Transactie A raakt eerst tabel 1 en dan tabel 2, transactie B andersom. Toegang in dezelfde volgorde is de klassieke preventie.

  • Lange transacties en gebruikersinteractie binnen een transactie. Zolang een transactie openstaat, blijven locks staan. Een dialoogvenster middenin een open transactie is dodelijk voor de concurrency.

  • Lock escalation. Zodra het aantal locks te hoog wordt, promoveert SQL Server naar een tabel- of partitielock. Vanaf dat moment ligt de hele tabel op slot voor concurrent werk.

  • Foreign keys en indexed views. Wijzig je kolommen waarnaar een FOREIGN KEY verwijst, dan moet de engine gerelateerde rijen opzoeken — en daar kunnen geen rijversies voor gebruikt worden. Bij cascading updates of deletes kan het isolatieniveau voor de duur van het statement zelfs naar serializable gaan. Indexed views over meerdere tabellen vragen extra locks bij elke wijziging.

  • Lock hints in applicatiecode. HOLDLOCK, SERIALIZABLE, REPEATABLEREAD, READCOMMITTEDLOCK, PAGLOCK, TABLOCK, UPDLOCK en XLOCK verhogen allemaal het risico.

Lock escalation wordt getriggerd zodra één statement meer dan 5.000 locks op één object aanvraagt, of bij geheugendruk. Kan er door lock-conflicten niet geëscaleerd worden, dan probeert de engine het opnieuw bij elke 1.250 nieuwe locks. (Bron: Microsoft Learn, Resolve blocking problem caused by lock escalation)

Let ook op gepartitioneerde tabellen: staat LOCK_ESCALATION op AUTO, dan lockt de engine op partitieniveau. Houden twee transacties elk een lock op hun eigen partitie en willen ze iets in de andere, dan ontstaat precies daar een deadlock. Zetten op TABLE voorkomt dat, maar kost concurrency — een afweging, geen standaardadvies.

Zie je naast deadlocks ook bredere traagheid, hoge I/O of oplopende wachttijden? Dan liggen de oorzaken vaak in hetzelfde vlak. Daarover schreven we eerder in SQL Server traag? 5 quick wins.

Hoe vind je de deadlock terug in SQL Server?

De goede aanpak begint met bewijs, niet met een vermoeden.

On-premises SQL Server (2022, 2025) en Azure SQL Managed Instance

De system_health-sessie draait standaard en legt het xml_deadlock_report-event al vast. Je hoeft dus meestal niets aan te zetten om te achterhalen wat er vannacht is gebeurd:

SELECT
    xdr.value('@timestamp', 'datetime')  AS deadlock_time,
    xdr.query('.')                       AS deadlock_report
FROM (
    SELECT CAST(xt.target_data AS XML) AS target_data
    FROM sys.dm_xe_session_targets AS xt
    JOIN sys.dm_xe_sessions AS xs
        ON xs.address = xt.event_session_address
    WHERE xs.name = N'system_health'
      AND xt.target_name = N'ring_buffer'
) AS x
CROSS APPLY target_data.nodes(
    'RingBufferTarget/event[@name="xml_deadlock_report"]') AS d(xdr)
ORDER BY deadlock_time DESC;

Sla de XML op met de extensie .xdl en open het bestand opnieuw in SSMS: je krijgt dan de grafische weergave.

Azure SQL Database en SQL database in Fabric

Daar leg je het sqlserver.database_xml_deadlock_report-event vast in een eigen event session, met een ring buffer target (eenvoudig, maar leeg na een failover) of een event file target in Azure Storage (blijft bewaard):

CREATE EVENT SESSION [deadlocks] ON DATABASE
ADD EVENT sqlserver.database_xml_deadlock_report
ADD TARGET package0.ring_buffer
WITH (STARTUP_STATE = ON, MAX_MEMORY = 4 MB);
GO
ALTER EVENT SESSION [deadlocks] ON DATABASE STATE = START;

Over trace flags 1204 en 1222: die schrijven deadlockinformatie naar het SQL Server errorlog, maar Microsoft raadt ze expliciet af op zwaarbelaste systemen vanwege performance-impact. Gebruik Extended Events. De volledige uitleg staat in de Deadlocks guide van Microsoft Learn.

Hoe lees je een deadlock graph?

Een deadlock graph heeft drie knopen:

  • victim-list — welk proces is teruggedraaid.

  • process-list — alle betrokken processen: sessie-ID, isolatieniveau, logused, clientapplicatie, hostname, login, de query plan hash en de input buffer.

  • resource-list — welke lock resources wie bezit en waarop gewacht wordt, inclusief objectname, indexname en de lock mode (S, U, X, IS, IX, SIX).

De praktische leesvolgorde: begin bij het slachtoffer, zoek in de resource-list wat het bezat en waarop het wachtte, en doe hetzelfde voor de tegenpartij. Daarmee heb je de cyclus, en vrijwel altijd de betrokken index.

Twee beperkingen die je moet kennen:

  • De input buffer is beperkt tot de eerste 4.000 tekens van het statement.

  • Alleen het statement dat de deadlock veroorzaakte staat in de graph. Draaide de transactie eerder al een ander statement dat de blokkade opbouwde, dan zie je dat níet terug. Voor het volledige beeld heb je de storedprocedure- of applicatiecode nodig, of een eigen Extended Events-trace.

De query plan hash uit de graph kun je gebruiken om het bijbehorende uitvoeringsplan uit Query Store te halen. Daar zie je of de query scant waar hij zou moeten seeken.

Hoe los je een SQL deadlock op?

Werk van laag risico naar hoog risico. Dat is ook de volgorde die Microsoft aanhoudt.

1. Indexen afstemmen — laagste risico

Microsoft noemt het afstemmen van non-clustered indexen de aanpak met het laagste risico, precies omdat je geen queries herschrijft en dus geen kans loopt op verkeerde resultaten. Controleer eerst of de tabel überhaupt een clustered index heeft — heaps ontstaan vaker per ongeluk dan bewust:

EXEC sp_helpindex 'dbo.JouwTabel';

Staat er geen “clustered” in de index_description, dan is het een heap. Zorg daarnaast dat indexen op de verwijzende tabel van een foreign key het opzoeken van gerelateerde rijen ondersteunen.

2. Row versioning inschakelen (RCSI)

Met READ_COMMITTED_SNAPSHOT aan gebruikt read committed rijversies in plaats van shared locks. Lezers blokkeren schrijvers niet meer, en andersom. Microsoft beveelt row versioning-based read committed aan voor vrijwel alle applicaties:

ALTER DATABASE [JouwDatabase]
SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;

Maar dit is geen knop die je zomaar omzet. Applicatiecode kan verwachten dat lezers wél geblokkeerd worden; verdwijnt die blokkering, dan kunnen race conditions verkeerde resultaten opleveren. Staat RCSI bewust uit, zoek dan eerst uit waárom voordat je hem aanzet. En houd er rekening mee dat versiebeheer extra ruimte in tempdb vraagt.

3. Transacties korter maken en volgorde gelijktrekken

Haal gebruikersinteractie uit transacties, houd ze in één batch, en laat concurrent transacties objecten in dezelfde volgorde benaderen. Storedprocedures helpen om die volgorde af te dwingen.

4. Snapshot isolation voor specifieke transacties

Vooral effectief voor SELECT-statements in databases waar RCSI uitstaat. Let op: in databases mét RCSI kan snapshot isolation een deadlock omzetten in een update conflict — de transactie moet dan alsnog opnieuw.

5. Een plan forceren via Query Store

Treedt de deadlock alleen op bij één specifiek uitvoeringsplan, dan kun je dat plan forceren. Effectief, maar het is een pleister: hij bevriest een plan waar meestal een index-probleem onder zit.

6. Retry-logica in de applicatie — altijd

Deadlocks zijn nooit volledig uit te sluiten. Elke applicatie die T-SQL uitvoert, hoort foutcode 1205 af te vangen. Zonder afhandeling loopt de applicatie door zonder te weten dat de transactie is teruggedraaid:

BEGIN TRY
    BEGIN TRANSACTION;
        -- wijzigingen
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF ERROR_NUMBER() = 1205
    BEGIN
        IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
        -- korte, gerandomiseerde pauze en opnieuw proberen
    END
    ELSE THROW;
END CATCH

Microsoft adviseert een korte pauze vóór de retry, met een willekeurige duur — bijvoorbeeld tussen één en drie seconden. Vaste wachttijden zorgen ervoor dat beide partijen tegelijk terugkomen en meteen opnieuw deadlocken.

Wat géén oplossing is:

  • DEADLOCK_PRIORITY aanpassen voorkomt de deadlock niet. Het bepaalt alleen wie er sneuvelt. Nuttig als één proces per se moet slagen, verder niet.

  • Lock escalation uitzetten verplaatst het probleem naar lockgeheugen en kan alsnog naar tabelniveau escaleren.

  • Zwaardere hardware. Een deadlock is een cyclus, geen capaciteitsprobleem.

Wat verandert optimized locking in SQL Server 2025 en Azure SQL?

Optimized locking is de grootste wijziging in het lockgedrag van de engine in jaren. Volgens de documentatie over optimized locking bestaat het uit twee delen:

  • Transaction ID (TID) locking — in plaats van veel key- of rowlocks tot het einde van de transactie, wordt één lock op de transactie-ID vastgehouden. Rij- en paginalocks worden losgelaten zodra de rij gewijzigd is. Bij het bijwerken van 1.000 rijen blijft er zo één X-lock op de TID over in plaats van 1.000 rijlocks tot commit.

  • Lock after qualification (LAQ) — predicaten worden geëvalueerd op de laatst gecommitte rijversie, zónder lock. Alleen als de rij kwalificeert, wordt er gelockt. LAQ werkt alleen als RCSI aanstaat.

Het effect: minder lockgeheugen, veel minder lock escalation en bepaalde deadlocktypes verdwijnen. Het klassieke voorbeeld met update-locks veroorzaakt geen deadlock meer, omdat U-locks niet meer gebruikt worden.

Beschikbaarheid (bron: Microsoft Learn):

  • Azure SQL Database, SQL database in Fabric en Azure SQL Managed Instance (Always-up-to-date en SQL Server 2025 update policy): beschikbaar en altijd aan.

  • SQL Server 2025 (17.x): beschikbaar, standaard uít, per database in te schakelen.

  • SQL Server 2022 (16.x) en ouder en Managed Instance met 2022-updatepolicy: niet beschikbaar.

SELECT name,
       is_accelerated_database_recovery_on,
       is_read_committed_snapshot_on,
       is_optimized_locking_on
FROM sys.databases
WHERE name = DB_NAME();

Inschakelen kan pas als Accelerated Database Recovery (ADR) aanstaat:

ALTER DATABASE [JouwDatabase] SET OPTIMIZED_LOCKING = ON;

En nu het deel dat je moet weten voordat je dit aanzet. LAQ kan bij concurrent workloads onder RCSI tot andere resultaten leiden. Microsoft geeft zelf het voorbeeld waarin een tweede transactie een rij overslaat omdat het predicaat op de laatst gecommitte versie niet matcht — zónder LAQ zou die transactie gewacht hebben en de rij wél bijgewerkt hebben. Applicaties die op strikte uitvoeringsvolgorde leunen, horen strengere isolatieniveaus te gebruiken. Voor pakketsoftware waarvan je de transactielogica niet kent, is dit een reden om eerst te testen en de leverancier te raadplegen.

Verder wordt LAQ niet gebruikt bij lock hints als UPDLOCK, XLOCK, HOLDLOCK en READCOMMITTEDLOCK, bij een ander isolatieniveau dan read committed, bij columnstore-indexen, bij MERGE, en bij statements met variabeletoewijzing of een OUTPUT-clausule.

Diagnose verandert ook: in de deadlock graph verschijnt een xactlock-element met de onderliggende resource, en je ziet nieuwe wachttypes als LCK_M_S_XACT_READ en LCK_M_S_XACT_MODIFY. Weet dat, anders lees je de graph verkeerd.

Draait het op SQL Server Express? Let hierop

Express veroorzaakt geen deadlocks, maar maakt de gevolgen wel zichtbaarder.

De limieten die ertoe doen:

  • Compute: de kleinste van 1 socket of 4 cores.

  • Bufferpoolgeheugen: ongeveer 1.410 MB per instance. Een scan die op Standard uit cache komt, gaat op Express naar disk.

  • Geen SQL Server Agent. Geen gepland index- en statistiekenonderhoud, tenzij je het zelf via Taakplanner of scripts regelt. Verwaarloosde statistieken leiden tot slechte plannen, en slechte plannen scannen.

  • Maximale databasegrootte: 10 GB in SQL Server 2022, en 50 GB in SQL Server 2025 — een van de grotere wijzigingen in die release.

Draai je een pakket met meerdere gelijktijdige gebruikers op Express, dan is onderhoud geen luxe. Het is precies wat scans en dus deadlocks voorkomt.

Twijfel je of Express nog past bij je aantal gebruikers en datavolume? Die afweging werkten we uit in SQL Server Express, Standard of Enterprise: welke editie past bij jouw organisatie?.

Applicatieleverancier of beheerder: wie lost het op?

Hier ontstaat de klassieke patstelling: de softwareleverancier wijst naar de infrastructuur, de IT-partij naar de applicatie, en de klant blijft met de foutmelding zitten.

Wij trekken dat grijs naar zwart-wit door de verantwoordelijkheid te pakken. Dat betekent concreet: wij doen de analyse, ongeacht waar de oorzaak uiteindelijk ligt.

Wat op de databasekant thuishoort:

  • Clustered en non-clustered indexen, statistieken en onderhoud

  • Databaseopties zoals RCSI, ADR en optimized locking

  • Query Store, monitoring en het vastleggen van deadlock graphs

  • Serverconfiguratie, geheugen en tempdb

Wat bij de leverancier hoort:

  • De volgorde waarin transacties objecten benaderen

  • De lengte van transacties en gebruikersinteractie daarbinnen

  • Lock hints in de applicatiecode

  • Retry-logica op foutcode 1205

Het verschil zit in hoe je die tweede categorie overdraagt. Een melding “jullie software geeft deadlocks” levert niets op. Een deadlock graph met de betrokken index, de lock modes, het uitvoeringsplan uit Query Store en de vastgestelde toegangsvolgorde levert wél een ticket op dat een leverancier kan oppakken. Dat bewijsmateriaal leveren wij aan, en wij voeren dat gesprek. Meer over onze werkwijze lees je op IT-beheer in Dordrecht.

Wat Sennin-IT voor jouw organisatie doet

Wij zorgen voor IT die gewoon werkt – veilig, stabiel en zonder gedoe. Geen helpdeskgedoe en geen standaardoplossingen, maar directe communicatie en maatwerk. Bij databaseproblemen betekent dat: eerst meten, dan pas ingrijpen, en geen wijzigingen op productie zonder dat we weten wat ze doen.

Concreet richten we deadlockdetectie in, analyseren we de graphs, optimaliseren we indexen en databaseopties, en nemen we het gesprek met je softwareleverancier over als de oorzaak in de applicatie ligt. Ook het onderliggende beheer — Windows Server, virtualisatie, back-up en monitoring — pakken we in samenhang op.

Loopt jouw applicatie met enige regelmaat vast op foutmelding 1205, of weet je niet of het om deadlocks of blokkering gaat? Leg het eens voor: Gratis IT-adviesgesprek plannen.

Veelgestelde vragen

Is een deadlock hetzelfde als een time-out?

Nee. Bij een deadlock beëindigt SQL Server zelf één transactie met foutmelding 1205. Een time-out komt van LOCK_TIMEOUT of van de clientapplicatie, terwijl de transactie nog gewoon staat te wachten. Standaard verlopen transacties in de database engine niet vanzelf.

Hoeveel deadlocks zijn acceptabel?

Eén afgehandelde deadlock vraagt geen actie: de engine heeft het conflict opgelost en de applicatie hoort de transactie opnieuw aan te bieden. Een terugkerend patroon is wél reden voor analyse.

Helpt een snellere server tegen deadlocks?

Nauwelijks. Een deadlock is een cyclische lock-afhankelijkheid, geen tekort aan rekenkracht. Snellere hardware verkort transacties en verkleint het tijdvenster waarin de cyclus kan ontstaan, maar neemt de oorzaak niet weg.

Verdwijnen deadlocks met SQL Server 2025 of Azure SQL?

Nee. Optimized locking vermindert lock escalation en voorkomt bepaalde deadlocktypes, maar deadlocks blijven mogelijk. Retry-logica in de applicatie blijft noodzakelijk.

 
 
 

Opmerkingen


bottom of page