aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Gameserver

MySQL Deadlock: wat het is en hoe je het oplost

Een MySQL deadlock ontstaat wanneer twee of meer transacties dezelfde rijen in tegengestelde volgorde proberen te vergrendelen en eindeloos op elkaar blijven wachten. InnoDB detecteert deze impasse, kiest één kant als het "slachtoffer" en draait die terug; je applicatie ziet dan de fout Deadlock found when trying to get lock; try restarting transaction. Op een gameserver, een e-commerce-backend of elk systeem met veel schrijfverkeer voelt deze fout onvermijdelijk, maar de overgrote meerderheid van deadlocks is te voorkomen met beter transactieontwerp.

Wat is een deadlock precies?

Het klassieke scenario gaat zo: transactie A vergrendelt eerst rij 1 en vraagt daarna om rij 2. Op datzelfde moment heeft transactie B al rij 2 vergrendeld en vraagt nu om rij 1. Beide wachten op een vergrendeling die de ander vasthoudt, dus niemand kan verder. Dit is een circular wait (cirkelvormig wachten).

Een belangrijk onderscheid: een deadlock is niet hetzelfde als een lock wait timeout. Bij een lock wait timeout wacht één transactie tot een vergrendeling vrijkomt en krijgt een fout zodra innodb_lock_wait_timeout (standaard 50 seconden) verloopt. Bij een deadlock is het wachten cirkelvormig, dus InnoDB wacht helemaal niet — het grijpt direct in en draait één kant terug.

Waarom ontstaan InnoDB-vergrendelingen?

InnoDB gebruikt vergrendeling op rijniveau, maar vergrendelingen gaan niet altijd over "precies één rij". Een paar belangrijke gedragingen veroorzaken deadlocks:

  • UPDATE/DELETE zonder index: als een WHERE-clausule geen geschikte index kan gebruiken, vergrendelt InnoDB elke rij die het doorloopt. Het lijkt of je één rij wijzigt, maar je hebt er mogelijk duizenden vergrendeld.
  • Gap- en next-key-locks: op het isolatieniveau REPEATABLE READ (de standaard van MySQL) vergrendelt InnoDB niet alleen rijen, maar ook de "gaten" ertussen. Dit voorkomt phantom reads maar creëert onverwachte conflicten.
  • Foreign key- en unieke-indexcontroles: bij het invoegen of bijwerken van een rij vergrendelt InnoDB ook de bijbehorende parent/child-sleutelrijen.
  • Inconsistente toegangsvolgorde: transacties die dezelfde tabellen in verschillende volgordes bijwerken in verschillende codepaden zijn veruit de meest voorkomende oorzaak van deadlocks.

Het deadlock-log lezen

Lees, voordat je gokt, wat InnoDB je vertelt. Je krijgt de details van de laatste deadlock met:

SHOW ENGINE INNODB STATUS\G

Zoek het gedeelte LATEST DETECTED DEADLOCK in de uitvoer. Het toont de twee transacties, de vergrendelingen die ze vasthouden (HOLDS THE LOCK(S)), de vergrendelingen waarop ze wachten (WAITING FOR THIS LOCK) en welke query liep. Zien welke tabel en welke index vergrendeld raakten, is de helft van de oplossing.

Om elke deadlock blijvend te loggen, voeg je dit toe aan je MySQL-configuratie:

[mysqld]
innodb_print_all_deadlocks = ON

Nu belandt elke deadlock in het MySQL-foutlog, zodat je ook zeldzame conflicten achteraf kunt onderzoeken.

Oplossing 1: leg een consistente transactievolgorde vast

De meeste deadlocks komen door een inconsistente vergrendelingsvolgorde. De oplossing is simpel maar vraagt discipline: laat elke transactie bronnen steeds in dezelfde volgorde aanraken. Bijvoorbeeld, bij het overboeken van saldo tussen twee rekeningen vergrendel je rijen altijd van de kleinste id naar de grootste:

START TRANSACTION;

-- De kleinste id wordt altijd eerst vergrendeld
UPDATE accounts SET balance = balance - 100
  WHERE id = LEAST(@from, @to);
UPDATE accounts SET balance = balance + 100
  WHERE id = GREATEST(@from, @to);

COMMIT;

Zelfs als twee transacties dezelfde twee rijen aanraken, kan er geen cirkelvormig wachten ontstaan omdat beide nu in dezelfde volgorde verlopen. Deze regel vereist dat je overal in je code dezelfde toegangsvolgorde toepast.

Oplossing 2: houd transacties klein en kort

Hoe langer een transactie openstaat, hoe langer ze haar vergrendelingen vasthoudt en hoe groter de kans op een conflict. Praktische regels:

  • Doe geen HTTP-verzoeken, bestandsschrijfacties of externe API-aanroepen binnen een transactie. Haal traag werk eruit.
  • Plaats alleen schrijfacties die echt atomair moeten zijn in één transactie.
  • Gebruik SELECT ... FOR UPDATE niet onnodig; vergrendel alleen de rij die je daadwerkelijk gaat bijwerken.
  • Splits bulkupdates in kleine batches in plaats van één gigantische query.

Oplossing 3: voeg de juiste indexen toe

Een WHERE-clausule zonder index dwingt InnoDB veel meer rijen te vergrendelen dan nodig. Controleer met EXPLAIN welke index je query gebruikt:

EXPLAIN UPDATE orders SET status = 'shipped'
  WHERE customer_id = 42 AND status = 'paid';

Als de kolom type ALL toont, vindt er een volledige tabelscan plaats. Een geschikte samengestelde index op customer_id en status verlaagt het aantal vergrendelde rijen tot een minimum en vermindert het deadlock-risico aanzienlijk.

Oplossing 4: opnieuw proberen aan de applicatiekant

Deadlocks tot nul terugbrengen is vaak onrealistisch; zeldzame conflicten kunnen altijd voorkomen. Je applicatie moet de deadlock-fout dus opvangen en de transactie opnieuw proberen. In Laravel kan dat standaard:

use Illuminate\Support\Facades\DB;

// Derde argument: probeer tot 3 keer opnieuw bij een deadlock
DB::transaction(function () {
    DB::table('accounts')->where('id', 1)->decrement('balance', 100);
    DB::table('accounts')->where('id', 2)->increment('balance', 100);
}, 3);

Belangrijk punt: opnieuw proberen heeft alleen zin voor de kant die als slachtoffer is gekozen, en het werkt wanneer de transactie kort is. Retry is er niet om slecht ontwerp te verbergen, maar om de zeldzame, onvermijdelijke conflicten op te vangen.

Veelgestelde vragen

Veroorzaakt een deadlock gegevensverlies?

Nee. InnoDB draait de slachtoffertransactie volledig terug, dus er blijft geen halve wijziging achter. Het echte gevaar is dat je applicatie de fout opslokt en nooit opnieuw probeert, waardoor de wijziging van de gebruiker stilletjes verdwijnt. Juist daarom is retry-logica essentieel.

Lost het verhogen van innodb_lock_wait_timeout deadlocks op?

Nee. Die instelling geldt voor lock wait timeouts, niet voor deadlocks. InnoDB detecteert en lost deadlocks al direct op; de timeout verhogen beïnvloedt alleen timeout-scenario's en voorkomt geen echte deadlocks.

Beëindigt een tabelvergrendeling de deadlocks?

Technisch kan één grove vergrendeling conflicten verminderen, maar ze vernietigt de concurrency en vertraagt je server drastisch. De juiste aanpak zijn rijvergrendelingen in een consistente volgorde met korte transacties; een tabelvergrendeling is bijna altijd de verkeerde oplossing.

Terugkerende deadlocks op je server? We kunnen samen je InnoDB-logs bekijken en je transactievolgorde en indexen corrigeren. Neem contact op en laten we je database stabiel maken.

Bu kategorideki tüm yazılar →

Devamı için