Bir ADO.NET Uygulaması ile DeadLock Senaryosunun gerçekleştirilmesi
Önceki makalemizde (SQL Server 2000 DeadLock Yönetimi) bahsettiğimiz ve Query Analyzer ile test ettiğimiz senaryoyu bir .net uygulaması ile test edeceğiz.
ADO.NET SqlTransaction sınıfı transaction yönetimini yukarıda örnekde kullandığımız Transact-SQL kodları ile gerçekleştirir.
SqlTransaction nesnesinin BeginTransaction, CommitTransaction gibi metodları çağrıldıklarında tanıdık olduğumuz BEGIN TRAN ve COMMIT TRAN gibi t-sql kodlarını SQL Server üzerinde çalıştırırlar.
Aşağıda örneğimiz için gerekli olacak işlemleri gerçekleştirmek üzere oluşturulmuş bir sınıf yer almaktadır.
Imports System.Data.SqlClient Public Class SqlWorker
Private con1 As New SqlConnection Private cmd1 As SqlCommand Private tran1 As SqlTransaction Public Sub New(ByVal name As String) con1.ConnectionString = "server=.;database=northwind;user id=sa;password=;Application Name=TransactionBaglantisi" & name & ";" End Sub Public Sub BeginTran()
Try con1.Open() cmd1 = New SqlCommand("", con1) tran1 = con1.BeginTransaction cmd1.Transaction = tran1 Catch ex As Exception RaiseEvent WorkerException(ex) End Try
End Sub
Public Sub Set_DEADLOCK_PRIORITY_LOW()
Try cmd1.CommandText = "SET DEADLOCK_PRIORITY LOW" cmd1.ExecuteNonQuery() RaiseEvent CommandExecuted(cmd1.CommandText) Catch ex As Exception RaiseEvent WorkerException(ex) End Try
Public Sub UseProducts()
Try cmd1.CommandText = "Update Products SET ProductName=ProductName WHERE ProductID=1" cmd1.ExecuteNonQuery() RaiseEvent CommandExecuted(cmd1.CommandText) Catch ex As Exception RaiseEvent WorkerException(ex) End Try
Public Sub UseCategories()
Try cmd1.CommandText = "Update Categories SET CategoryName=CategoryName WHERE CategoryID=1" cmd1.ExecuteNonQuery() RaiseEvent CommandExecuted(cmd1.CommandText) Catch ex As Exception RaiseEvent WorkerException(ex) End Try
Public Sub CommitTran() Try tran1.Commit() con1.Close() Catch ex As Exception RaiseEvent WorkerException(ex) End Try End Sub
Public Event WorkerException(ByVal ex As Exception) Public Event CommandExecuted(ByVal str As String)
End Class
Constructor (Sub New) : Parametre olarak aldığı değeri connectionstring içerisinde Application Name olarak kullanır. SQL Profiler ile izleme yaparken kullanılmak üzere bu değer değiştirilmiştir.
BeginTran metodu : Connection'ı açıp, transactionı başlatır.
Set_DEADLOCK_PRIORITY_LOW metodu : Çağrıldığında SET DEADLOCK_PRIORITY LOW komutunu çalıştırılarak ait olduğu transactionı, olası bir deadlock durumunda kurban adayı olarak öne çıkarır.
UseProducts ve UseCategories metodları : Amaçları sadece tabloların ait oldukları transaction tarafından kilitlenmesini sağlamaktır. Kayıtlar üzerinde değişiklik yapmayan UPDATE sorgularını çalıştırırlar.
CommitTran metodu: BeginTran ile başlatılmış olan transactionı onaylamak için kullanılan metoddur.
Yukarıdaki sınıfı kullanmak ve test işlemini gerçekleştirmek için aşağıdaki windows formunu tasarlayalım.
Yukarıdaki form içerisine aşağıdaki kodları ekleyelim.
Private Sub logla(ByVal str As String) lbActions.Items.Add(str) lbActions.SelectedIndex = lbActions.Items.Count - 1 End Sub
Dim WithEvents worker1 As SqlWorker Dim WithEvents worker2 As SqlWorker Private Sub btnTran1_1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTran1_1.Click
If worker1 Is Nothing Then worker1 = New SqlWorker("worker1") worker1.BeginTran() If chkTran1LowPriority.Checked Then worker1.Set_DEADLOCK_PRIORITY_LOW() End If logla("Transaction1 başlatıldı.")
Private Sub btnTran1_2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTran1_2.Click
worker1.UseProducts() logla("Transaction1 : Products tablosu üzerinde işlem gerçekleştirildi.")
Private Sub btnTran1_3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTran1_3.Click
Dim th As New Threading.Thread(AddressOf worker1.UseCategories) th.Start() logla("Transaction1 : Categories tablosu üzerinde işlem gerçekleştirildi.")
Private Sub btnTran1_4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTran1_4.Click
worker1.CommitTran() logla("Transaction1 sonladırıldı. İlgili connection kapandı.")
Private Sub btnTran2_1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTran2_1.Click
If worker2 Is Nothing Then worker2 = New SqlWorker("worker2") worker2.BeginTran() If chkTran2LowPriority.Checked Then worker2.Set_DEADLOCK_PRIORITY_LOW() End If logla("Transaction2 başlatıldı.") End Sub
Private Sub btnTran2_2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTran2_2.Click
worker2.UseCategories() logla("Transaction2 : Categories tablosu üzerinde işlem gerçekleştirildi.")
Private Sub btnTran2_3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTran2_3.Click
Dim th As New Threading.Thread(AddressOf worker2.UseProducts) th.Start() logla("Transaction2 : Products tablosu üzerinde işlem gerçekleştirildi.")
Private Sub btnTran2_4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTran2_4.Click
worker2.CommitTran() logla("Transaction2 sonladırıldı. İlgili connection kapandı.")
Private Sub worker1_WorkerException(ByVal ex As System.Exception) Handles worker1.WorkerException
Dim oldColor As Color = GroupBox1.BackColor GroupBox1.BackColor = Color.Red logla(ex.Message) MessageBox.Show(ex.Message, "worker1") worker1 = Nothing worker2.CommitTran() lbActions.Items.Clear() GroupBox1.BackColor = oldColor
Private Sub worker2_WorkerException(ByVal ex As System.Exception) Handles worker2.WorkerException
Dim oldColor As Color = GroupBox2.BackColor GroupBox2.BackColor = Color.Red logla(ex.Message) MessageBox.Show(ex.Message, "worker2") worker2 = Nothing worker1.CommitTran() lbActions.Items.Clear() GroupBox2.BackColor = oldColor
Private Sub worker1_CommandExecuted(ByVal str As String) Handles worker1.CommandExecuted logla("worker1 : " & str) End Sub Private Sub worker2_CommandExecuted(ByVal str As String) Handles worker2.CommandExecuted logla("worker2 : " & str) End Sub
Yukarıdaki kodlar oluşturduğumuz SqlWorker sınıfının metodlarını sırası ile çağıran butonlar için gerekli kodları içermektedir. İncelendiğinde gayet kolay anlaşılacak şekilde basit metod çağrıları içermektedir. İki transaction içerisindeki 3. butonlar metodlarını yeni bir thread içerisinde çağıracak şekilde yazılmıştır. Çünkü bir butona tıklandığında timeout hatası vermeden diğer butona tıklanamaz, eğer metodlar aynı threadde çağrılmış olsaydı.
Şimdi aşağıdaki sıra ile butonlara tıklayarak deadlock oluşmasını sağlayın.
Transaction1 içindeki 1.buton
Transaciton2 içindeki 2.buton
Transaction1 içindeki 2.buton
Transaction2 içindeki 2.buton
Transaction1 içindeki 3.buton
Transaction2 içindeki 3.buton
5. ve 6. adımlar arasındaki tıklama süreniz 5.adımdaki metod çağrısının zaman aşımına uğramayacağı kadar kısa bir sürede olmalıdır. (Varsayılan connection timeout süresi 15 sn.dir.)
Bu işlemleri gerçekleştirdğiniz de aşağıdaki gibi bir ekran ile karşılaşmış olmalısınız. Deadlock hatasının oluştuğunu görebiliyoruz.
Yukarıda ekran görüntüsünü kaydettiğim denemem de SQL Server Transaction1 içinde başlatılan transaction'ı kurban olarak belirledir. Siz istediğiniz transaction'ın kurban olarak seçilmesi için SET DEADLOCK_PRIORITY LOW checkbox'ını işaretleyerek test edebilirsiniz.
SQL Server Profiler aracı ile DeadLock durumunun izlenmesi
Aşağıdaki resimde gözüktüğü gibi bir Trace başlatarak uygulamamız sonucunda oluşan deadlock durumunu SQL Server Profiler aracı ile izleyebiliriz.
ApplicationName değerine SqlWorker sınıfında connectionstring üzerinde verdiğimiz değerleri vererek sadece bizim uygulamamızdan gelen sorguların izlenmesini istiyorum. Bu şekilde kafamı karıştıracak diğer uygulamarın gönderdiği komutları izlemek zorunda kalmayacağım.
Yukarıdaki yapılandırma ile trace işlemini başlattıktan sonra uygulamamızı çalıştırarak daha önce bahsettiğimiz gibi deadlock oluşması için ilgili adımları takip edin.
Yukarıdaki resimde SQL Profiler aracı ile DeadLock durumunu gözlemeleyebildiğimizi görüyoruz. Profiler ile gözlemlediğimiz deadlock durumlarının çözmek için ilgili sql sorgularını ilk makalede bahsettiğimiz gibi hangi kodlarınızda deadlock oluşmasına müsait bir tasarım olduğunu tespit edebilirsiniz.
DeadLock durumuna karşı alabileceğimiz ilk önlem, transactionlarımız içindeki sorgularımızın çalıştırılma sırası olacaktır. Ayrıca uygulama katmanında 1205 nolu hatayı yakalayıp eğer transactionınız kurban olarak seçildi ve rollback yapıldıysa transactionı tekrar çağırabilirsiniz.
Cengiz HAN Microsoft ASP.NET MVP cengiz@cengizhan.com