Bir MySQL deadlock, iki ya da daha fazla transaction'ın aynı satırları ters sırayla kilitlemeye çalışıp birbirini sonsuza dek beklemesi durumudur. InnoDB bu çıkmazı tespit eder, taraflardan birini "kurban" seçer ve geri alır; uygulamanız da o anda Deadlock found when trying to get lock; try restarting transaction hatasını görür. Oyun sunucusu, e-ticaret ya da herhangi bir yoğun yazma trafiği olan sistemde bu hata kaçınılmaz gibi durur, ama büyük çoğunluğu transaction tasarımıyla önlenebilir.
Deadlock tam olarak nedir?
Klasik senaryo şudur: A transaction'ı önce satır 1'i kilitler, sonra satır 2'yi ister. Aynı anda B transaction'ı önce satır 2'yi kilitlemiştir ve şimdi satır 1'i ister. İkisi de diğerinin bıraktığı kilidi beklediği için ortada ilerleyebilecek kimse kalmaz. Bu bir circular wait (dairesel bekleme) durumudur.
Önemli ayrım: deadlock ile lock wait timeout aynı şey değildir. Lock wait timeout'ta tek bir transaction kilidin serbest kalmasını bekler ve innodb_lock_wait_timeout süresi (varsayılan 50 saniye) dolunca hata alır. Deadlock'ta ise bekleme dairesel olduğu için InnoDB beklemeden, anında müdahale eder ve birini geri alır.
InnoDB kilitleri neden ortaya çıkar?
InnoDB satır bazlı kilitleme kullanır, ama kilitler her zaman "tam tek satır" değildir. Birkaç önemli davranış deadlock üretir:
- İndekssiz UPDATE/DELETE: Bir
WHEREkoşulu uygun indeksi kullanamazsa InnoDB taradığı tüm satırları kilitler. Tek satır günceller gibi görünürsünüz, aslında binlerce satırı kilitlemiş olursunuz. - Gap ve next-key kilitleri:
REPEATABLE READizolasyon seviyesinde (MySQL varsayılanı) InnoDB sadece satırları değil, satır aralarındaki "boşlukları" da kilitler. Bu, hayalet okumaları önler ama beklenmedik çakışmalar yaratır. - Foreign key ve unique index kontrolleri: Bir satır eklerken ya da güncellerken InnoDB ilgili ana/yabancı anahtar satırlarını da kilitler.
- Farklı sıralı erişim: Aynı tabloları farklı kod yollarında farklı sırayla güncelleyen transaction'lar deadlock için en sık görülen sebeptir.
Deadlock log'unu okumak
Tahmin yürütmeden önce InnoDB'nin size ne söylediğini okuyun. En son deadlock'un detayını şu komutla alırsınız:
SHOW ENGINE INNODB STATUS\G
Çıktıda LATEST DETECTED DEADLOCK bölümünü bulun. Burada iki transaction, tuttukları kilitler (HOLDS THE LOCK(S)), bekledikleri kilitler (WAITING FOR THIS LOCK) ve hangi sorgunun çalıştığı yazar. Hangi tablonun ve hangi indeksin kilitlendiğini görmek çözümün yarısıdır.
Tüm deadlock'ları kalıcı olarak log'a yazdırmak için MySQL yapılandırmanıza şunu ekleyin:
[mysqld]
innodb_print_all_deadlocks = ON
Böylece her deadlock MySQL error log'una düşer ve nadir görülen çakışmaları sonradan inceleyebilirsiniz.
Çözüm 1: Transaction sırasını sabitleyin
Deadlock'ların büyük kısmı tutarsız kilit sırasından kaynaklanır. Çözüm basit ama disiplin ister: tüm transaction'lar kaynaklara her zaman aynı sırayla dokunsun. Örneğin iki hesap arasında bakiye transferinde satırları her zaman küçük id'den büyüğe doğru kilitleyin:
START TRANSACTION;
-- Her zaman küçük id önce kilitlenir
UPDATE accounts SET balance = balance - 100
WHERE id = LEAST(@from, @to);
UPDATE accounts SET balance = balance + 100
WHERE id = GREATEST(@from, @to);
COMMIT;
İki transaction aynı iki satıra dokunsa bile artık ikisi de aynı sırayla ilerlediği için dairesel bekleme oluşamaz. Bu kural, kodunuzun her yerinde aynı erişim sırasını uygulamanızı gerektirir.
Çözüm 2: Transaction'ları küçük ve kısa tutun
Bir transaction ne kadar uzun açık kalırsa, tuttuğu kilitler o kadar uzun sürer ve çakışma olasılığı o kadar artar. Pratik kurallar:
- Transaction içinde HTTP isteği, dosya yazma ya da dış API çağrısı yapmayın. Yavaş işi transaction dışına alın.
- Sadece gerçekten atomik olması gereken yazmaları tek transaction'a koyun.
- Gereksiz
SELECT ... FOR UPDATEkullanmayın; sadece gerçekten güncelleyeceğiniz satırı kilitleyin. - Toplu güncellemeleri tek dev sorgu yerine küçük partilere (batch) bölün.
Çözüm 3: Doğru indeksler ekleyin
İndekssiz bir WHERE koşulu InnoDB'yi gereğinden fazla satır kilitlemeye zorlar. Sorgunuzun hangi indeksi kullandığını EXPLAIN ile kontrol edin:
EXPLAIN UPDATE orders SET status = 'shipped'
WHERE customer_id = 42 AND status = 'paid';
Eğer type sütunu ALL görünüyorsa tam tablo taraması yapılıyor demektir. customer_id ve status üzerine uygun bir bileşik indeks eklemek, kilitlenen satır sayısını minimuma indirir ve deadlock riskini ciddi şekilde azaltır.
Çözüm 4: Uygulama tarafında yeniden deneme (retry)
Deadlock'ları sıfıra indirmek çoğu zaman gerçekçi değildir; nadir çakışmalar her zaman olabilir. Bu yüzden uygulamanız deadlock hatasını yakalayıp transaction'ı yeniden denemelidir. Laravel'de bunu hazır şekilde yapabilirsiniz:
use Illuminate\Support\Facades\DB;
// Üçüncü parametre: deadlock'ta 3 kez yeniden dener
DB::transaction(function () {
DB::table('accounts')->where('id', 1)->decrement('balance', 100);
DB::table('accounts')->where('id', 2)->increment('balance', 100);
}, 3);
Önemli nokta: yeniden deneme yalnızca kurban seçilen taraf için anlamlıdır ve transaction kısa olduğunda işe yarar. Retry, kötü tasarımı gizlemek için değil, kaçınılmaz nadir çakışmaları yumuşatmak içindir.
Sık Sorulan Sorular
Deadlock veri kaybına yol açar mı?
Hayır. InnoDB kurban transaction'ı tamamen geri alır (rollback), yani yarım kalmış bir değişiklik kalmaz. Tehlike, uygulamanızın hatayı yutup işlemi hiç yeniden denememesi ve kullanıcının değişikliğinin sessizce kaybolmasıdır. Bu yüzden retry mantığı şarttır.
innodb_lock_wait_timeout değerini artırmak deadlock'ı çözer mi?
Hayır. O ayar lock wait timeout için geçerlidir, deadlock için değil. InnoDB deadlock'ı zaten anında tespit edip çözer; süreyi uzatmak yalnızca timeout senaryolarını etkiler ve gerçek deadlock'ları engellemez.
Tablo kilidi kullanırsam deadlock biter mi?
Teknik olarak tek bir kaba kilit çakışmayı azaltabilir ama eşzamanlılığı (concurrency) yok eder ve sunucunuzu ciddi şekilde yavaşlatır. Doğru yol, satır kilitlerini tutarlı bir sırayla ve kısa transaction'larla kullanmaktır; tablo kilidi neredeyse her zaman yanlış çözümdür.
Sunucunuzda tekrar eden deadlock'lar mı var? InnoDB log'larını birlikte inceleyip transaction sıranızı ve indekslerinizi düzeltebiliriz. Benimle iletişime geçin, veritabanınızı kararlı hale getirelim.