Ölümcül Kilitlenme (Deadlock)

Eyl 06, 2013
Ölümcül kilitlenme, iki veya daha fazla işlemin karşılıklı olarak birbirlerinin kilitlediği kaynaklara erişmek istemesiyle oluşur. Her iki işlem de sürekli birbirlerini beklediği için sistem kaynakları olumsuz yönde etkilenir. Özellikle sunucunun işlemci değeri boşuna harcanmış olur. Bu da, sunucunun performansını olumsuz yönde etkiler ve sunucuyu cevap veremez duruma bile getirebilir.

Aşağıdaki örnekte ölümcül kilitlenme ortaya çıkaracak bir senaryo oluşturulmuştur.

SQL Server’da bulunan bir veritabanı altında iki farklı tablo bulunmaktadır.

   

                       Musteri Tablosu                                          Personel Tablosu

Örnek senaryoda, içerisinde birden fazla güncelleştirme işlemi barındıran iki farklı prosedür oluşturulsun ve bu prosedürler eş zamanlı çalışıp her iki tabloyu ayrı ayrı kullanarak aşağıdaki işlemleri yapıyor olsun.

 1.adım    1. prosedür başlatılır.
 2.adım   2. prosedür başlatılır.
 3.adım   1. prosedür Personel tablosunda personelId değeri 67 olan satırı kitler ve günceller.
 4.adım 2. prosedür Musteri tablosunda musteriId değeri 223 olan satırı kitler ve günceller.
 5.adım 1. prosedür Musteri tablosundaki musteriId değeri 223 olan satırı güncellemek ister. Ancak, 2. prosedür bu satırı kitlediğinden, varsayılan kilitleme zaman aşımı (Lock TimeOut) süresi kadar bu kilidin açılmasını bekler. Bu süre sonuna kadar 2. prosedürün işlemlerini onaylaması veya geri alması beklenir.
 6.adım 2. prosedür Personel tablosundaki personelId değeri 67 olan satırı güncellemek ister. Ancak bu durumda, 1. prosedür bu satırı kitlediğinden yine varsayılan kilitleme zaman aşımı süresi kadar bu kilidin açılmasını bekler. Bu süre sonuna kadar 1. prosedürün işlemlerini onaylaması veya geri alması beklenir.

Böyle bir durumda bu iki prosedür tüm işlemlerini yaptıktan sonra verileri veritabanına yazar. Bu prosedürler kendi içerisinde transaction (daha küçük parçalara ayrılamayan en küçük işlem yığını) gibi çalışır. 5. ve 6. adımlar eş zamanlı olarak gerçekleştirildiklerinden, iki transaction da birbirinin kilitlerinin açılmasını beklemek durumundadır ve bu noktada ölümcül kilitlenme oluşur. Süreler sona erinceye kadar herhangi bir transaciton sahip olduğu işlemleri ne onaylamış (Commit) ne de geri almıştır (RollBack). Dolayısıyla transactionlardan birisi, varsayılan olarakta SQL Server'a göre en maliyetli olanı otomatik olarak geri alacaktır. Bu durumda bu transactiona ait kilitler ortadan kalktığından, kalan "transaction"a ait güncelleme işlemi tamamlanır. Ancak .NET ile yazılan uygulamalarda ölümcül kilitlenme oluştuğunda, "transaction"lardan birisi geri alınmakla kalmaz aynı zamanda ortama bir istisna tetiklenir. Dolayısıyla ölümcül kilitlenme durumunda bu istisnanında ele alınması gerekir ki, ölümcül kilitlenme sonucu çalışmaya devam eden "transaction" işlemleri onaylanabilsin.

Çözüm için ilk olarak 1. prosedür ve 2. prosedürdeki ilk güncelleme işlemlerinden sonra onaylama işlemi sağlamak ve kullanılan tablolardaki kiliti çözmek düşünülebilir. Ancak bu hatalı bir çözümdür. Çünkü süreç bitene kadar süreç içerisinde bir hata ya da istenmeyen bir durum oluştuğunda geri alma yapıldığında ancak onaylama işlemi yapılan yere kadar olan bölüm kurtarılabilir. Onaylamadan önceki bölümü kurtarmak için ayrı bir çalışma yapmak gerekmektedir. Onaylama işlemi sağlamanın bir diğer sakıncası ise her onaylama işleminden sonra veritabanında geri alma bölümleri oluşur yani iki onaylama işlemi arasında yapılan işlemler sunucu tarafından tutulur. Çok fazla onaylama işlemi yapılması hem prosedürün hızını yavaşlatır hemde sunucunun gereğinden fazla şişmesini sağlar.

Çözüm için bir diğer yöntem ise SQL Server üzerinde ölümcül kilitlenme olan süreçleri tespit edip, kapatmak olabilir. Burada özellikle SQL yöneticisine çok fazla iş düşer. Sp_who süreci çalıştırılarak ölümcül kilitlenen tablolar görülebilir. Management Studio -> Management -> ActivityMonitor seçilerek halen aktif olan kilitlenmeler görülüp süreçlerin üzerinde yok etme yapılarak ölümcül kilitlenmeler çözülebilir.

Diğer bir yöntem ise SQL Server'ın ölümcül kilitlenmeyi farketmesi halinde daha az önceliği olan süreci yok ederek bekleyen diğer sürece yol açması şeklindedir. SQL Server'ın hangi sürecin öncelikli olduğunu bilmesi için DEADLOCK_PRIORITY parametresinin LOW ya da NORMAL olarak eşitlemek gerekmektedir. Bu yöntemde her zaman ölümcül kilitlenmenin yakalanabileceği garanti değildir. Yakalansa bile bu yakalamaya kadar geçen süre çok uzun olduğu için sistem kaynakları olumsuz yönde etkilenecektir.

Tam olarak bir çözüm yöntemi olmasada en çok tercih edilen yöntem ise süreçlerin başlarında kullanılacak olan tabloların ilgili satırlarını bir grup biçiminde kendisi ile güncelleme yaparak kilitlenmesi ve daha sonra bu tablolar üzerinde işlemlerin yapılmasıdır. Yani Personel ve Musteri tabloları arasında yapılan işlemler ve bu işlemlerin gerçeklenme süreleri yok edilerek ölümcül kilitlenmeye girme olasılığının bitirilmesidir. 1. prosedür ilk başlandığında Personel ve Musteri tablolarının ilgili kayıtları kendileri ile güncellenerek kilitlenir. Aynı işlem 2. prosedür için de yapılır. 1. ve 2. prosedürden hangisi daha önce bu ilk adımı geçerse diğer süreç geçenin işlemlerini yapmasını bekleyecektir.

Ölümcül kilitlenmeye düşmemek için alınabilecek önlemler

  • Mümkün olduğunca az sayıda tablo üzerinde işlem yapacak prosedürler yazmak. Gereksiz işlemler ile prosedürü yormamak.
  • Prosedürlerin çok hızlı çalışmalarını sağlamak.
  • Süreç içerisinde yapılacak işlemleri mümkünse aynı tablo sırasında yapmak
  • WITH(NOLOCK) kullanılarak yapılan seçim işlemlerinde eğer o satır üzerinde kilit 
    varsa kilitleme işlemini gerçekleştiren prosedür işlemini tamamlamadan önceki veri alınır ve sorgu kilitlemeyi beklemek zorunda kalmaz. Fakat alınan verinin her an değişecek olması göz önüne alınmalıdır. Kullanılışı SELECT * FROM TABLO_ISMI WITH(NOLOCK) şeklindedir.