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
End Sub
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
End Sub
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
End Sub
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ı.")
End Sub
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.")
End Sub
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.")
End Sub
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ı.")
End Sub
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.")
End Sub
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.")
End Sub
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ı.")
End Sub
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
End Sub
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
End Sub
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