Bir ADO.NET Uygulaması ile DeadLock Senaryosunun gerçekleştirilmesi - Yazgelistir.com
Ö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.
Bir ADO.NET Uygulaması ile DeadLock Senaryosunun gerçekleştirilmesi
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.
Public Class SqlWorker
Private con1 As New SqlConnection
Private tran1 As SqlTransaction
con1.ConnectionString = "server=.;database=northwind;user id=sa;password=;Application Name=TransactionBaglantisi" & name & ";"
Public Sub BeginTran()
cmd1 = New SqlCommand("", con1)
tran1 = con1.BeginTransaction
cmd1.Transaction = tran1
Public Sub Set_DEADLOCK_PRIORITY_LOW()
cmd1.CommandText = "SET DEADLOCK_PRIORITY LOW"
Public Sub UseProducts()
cmd1.CommandText = "Update Products SET ProductName=ProductName WHERE ProductID=1"
Public Sub UseCategories()
cmd1.CommandText = "Update Categories SET CategoryName=CategoryName WHERE CategoryID=1"
Public Sub CommitTran()
Public Event WorkerException(ByVal ex As Exception)
Public Event CommandExecuted(ByVal str As String)
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.SelectedIndex = lbActions.Items.Count - 1
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")
If chkTran1LowPriority.Checked Then
Private Sub btnTran1_2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTran1_2.Click
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)
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
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")
If chkTran2LowPriority.Checked Then
Private Sub btnTran2_2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTran2_2.Click
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)
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
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
worker1 = Nothing
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
worker2 = Nothing
GroupBox2.BackColor = oldColor
Private Sub worker1_CommandExecuted(ByVal str As String) Handles worker1.CommandExecuted
logla("worker1 : " & str)
Private Sub worker2_CommandExecuted(ByVal str As String) Handles worker2.CommandExecuted
logla("worker2 : " & str)
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.