Alper ÇELİK

Bilgisayar Mühendisi

Sql Server Concurrent Transactions Problemleri

Transaction işlemleri esnasında veritabanı seviyesinde birtakım sorunlar çıkabilir. Bu yazımda olası çıkabilecek senaryolara göre bu sorunlar ve bunlara karşı alınabilecek çözümleri işliyor olacağız.
 
 Dirty Read  
Öncelikle bu kavramın ne olduğunu açıklayalım. Örneğin A transaction'ı bir update işlemine başladı diyelim ancak bir şekilde bahse konu işlem sonlanmıyor olsun (yani commit), senaryoya göre transaction işlemi başarılı şekilde sonlanabilirde ve yahut işlem gerçekleşmeyedebilir(yani rollback) işte buradaki belirsizliğin farklı bir transaction sorgusunda okunmasından açığa çıkan sonuç kümesine "dirty data", bu durumdan oluşan belirsizlik sonucu, farklı bir session'dan o veriyi okuma yöntemine'de "dirty read" diyoruz.
Konuyu örnek üzerinden görelim birde. tblUrun adında bir tablo oluşturup içersine üç kolondan oluşan tek satırlık veri tanımlıyorum.
 
Bakınız Resim-1.
 
(Karşılaştırmalı örneği ben aynı instance üzerinde 2 farklı session ile yapmayı tercih ettim.)
Kod bloğumu yazıp 15 saniyelik bir gecikme atadıktan sonra TRANSACTION 1'i ve sonra da TRANSACTION 2'yi çalıştırıyorum..TRAN 2'deki sorgum TRAN 1 biter bitmez çalışacaktır. Bunun sebebi sql server'ın default isolation seviyesinin "READ COMMITTED" olmasından ötürüdür. Yani daha açık bir ifadeyle tran 2 sorgusu bize diyor ki; "ben elbette bu veriyi okuyacağım ama ben okuyacağım verinin onaylı(committed) olmasına bakarım."
Anahtar Not: Bu durum her iki TRAN sorgusunun da aynı yöne(tablo, kolon vb) baktığı durumlar için geçerlidir.
 
Şimdi TRANSACTION 2 sorgum için commit kilitlenmesine maruz kalmamak için isolation level seviyesini "UNCOMMITTED" durumuna çeviriyorum ve her 2 tran sorgumuda t anında çalıştırıyorum.
 
Bu kez Resim-2'den de görüldüğü gibi tran 1 sorgum çalışmaya devam ederken tran 2 sorgusu commit'lenme ihtiyacı duymadığı için bana sonuç kümesini hızlı bir şekilde getirdi. İşte tran 2 deki bu sonuç kümesine "dirty data", yapmış olduğum SELECT okuma işleminede "dirty read" diyoruz.  
 
Not:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED kodunun eşleniği
SELECT * FROM tblUrun (NOLOCK) where UrunID= 1 olarak tanımlanabilirdi.  
 
Lost Update  
Bu problemin sebebi aynı data'yı birden fazla transaction'ın hem okuması hem de güncellemesinden ötürüdür. Resim-3'ü inceleyecek olursak..
 
Transaction 1 sorgumda 1 adet ve transaction 2 sorgumda'da 2 adet olmak üzere toplam 3 adet ürün satıyorum. İşlem sonunda tablomu kontrol ettiğimde 10-3=7 adet ürün kalmasını bekliyorum..Ancak kontrol ettiğimde stok adedimi 9 olarak görüyorum. Arada kaybolan 2 adet ürünüme ne oldu peki ? İşte tran 2 sorgusundaki veri bütünlüğünü bozan bu duruma "lost update" ismi veriliyor. Dilerseniz canlı örnek üzerinden bakalım birde:
 
Bakınız Resim-4.
 
Tran 1 sorgumda tblUrun tabloma SELECT attığımda stokta kalan adedimi 9 olarak görüyorum. Halbuki gerçek değerim tran 2 den de gelen güncelleme ile 7 olarak kalmasıdır. Bu şekilde aynı işlemi yapan transaction sorgularının farklı sonuçlar üretmesine "repeatable read" diyoruz.
Bu sorunu aşmak için sorgu cümleciğinin başına şu ifadeyi yazıyor olmamız gerekir:
 
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 
Bakınız Resim-5.
 
tblUrun tablomu tekrar 10 adet stok varmış gibi güncelledikten sonra repeatable read sorgu cümlemi tanımlıyorum. Önce tran 1'i sonra'da tran 2'yi çalıştırıyorum ve çıkan sonuç yukardaki resimde gördüğünüz gibi yansıyor. Önce tran 2 den çıkan hata mesajına bakalım..
"Transaction (Process ID 56) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction."
Yani diyor ki; "Benim çalışmak istediğim tablo üzerinde şuan başka bir transaction çalışıyor ben sıramı o her kimse ona verdim. Sıramı beklerim sorun yok. Daha sonra tekrar deneyiniz." Ve bu çağrı uyarınca tran 2 yi çalıştırdığımda başarılı bir şekilde güncelleme işlemim geliyor. Bu şekilde lost update işlemine çözüm bulmuş oluyoruz.  
 
 Non Repeatable Read
Alt başlığımızı örneklendirmeden önce tanımla ne olduğunu anlamaya çalışalım:
Aynı transaction içinde bir satırın birden fazla kere okunması(SELECT) esnasında farklı bir transaction bloğundan bu satıra güncelleme(UPDATE) veya silme(DELETE) yapılması vasıtasıyla oluşan hatalı sonuç kümelerine verilen isimdir.
Örnek üzerinden bakacak olursak:
 
Bakınız Resim-6.
 
Yukarıdaki resimde görüldüğü gibi tran 1 sorgum için 2 adet SELECT sorgusu yazıp, 10 saniyelik bir gecikmeyle sorgumun çalışması gerektiğini belirtiyorum. Resmin sağ tarafındaki tran 2 sorgumda ise aynı tablodaki aynı satır için bir güncelleme gerçekleştiriyorum. Bu haliyle önce tran 1 sorgumu ve sonrada tran 2 sorgumu çalıştırıyorum. Tran 2 sorgu sonucumun t anında güncelleme işlemimi yaptığını görüyorum. Öte yandan tran 1 sorgu sonucumun farklı değerler getirdiğini görüyorum.
İşte bu sorun veritabanı seviyesinde(Isolation Level), "repeatable read" sorunu olarak adlandırılıyor. Bu problemin çözümü tran 1 sorgumuzun yazdığı oturumda, "isolation level" seviyesini "repeatable read" yapmaktan geçiyor. Yani şu sorgu cümleciğini yazıyoruz;
 
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 
 Sorunu çözecek sorgumuza örnek üzerinden bakalım:
(Stok Adet miktarını tekrar 10 yapmayı unutmuyoruz.)  
 
Bakınız Resim-7.
 
Her iki tran sorgumu çalıştırdığımda bu kez tran 2 sorgusu tran 1'in sonlanmasını bekleyecek ve sonrasında güncelleme yapacaktır. Tran 1 den dönen değerlerim ise her iki sorgu için de stokadet=10 şeklinde olacaktır.
 
Phantom Reads  
Phantom read, bir transaction içersinde çalışan 2 sorgunun farklı bir transaction'dan gelen insert işlemi ile farklı sayıda satır sonucu döndürmesi sorunudur. Örnek üzerinden inceleyecek olursak:
 
Resim-8'den de görüldüğü üzere her iki sorguyu da eşzamanlı çalıştırdığımızda tran 2 sorgusu herhangi bir engele uğramadan kayıt işlemini yapıyor ve tran 1 sorgusunda farklı sayıda satır sayısına sahip sonuç kümesi elde ediyorum buda phantom read sorununa yol açıyor ve veri bütünlüğümüz bozuluyor.
Bu sorunu çözmek için isolation level'ın "snaphot" veya "serializable" durumunda olması gerekiyor. Böylelikle belirtilen aralığa herhangi bir kayıt atılamayacak ve alacağımız önlem ile veri girişi engellenmiş olacak. Sorunu çözecek sorgu cümleciği şu şekilde olmalıdır:
 
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
 
Örnek üzerinden inceleyecek olursak :
(Son kaydı silmeyi unutmayınız.)
 
Bakınız Resim-9.
 
Phantom read sorununu ortadan kaldıracak sorgu cümleciğimi yazdıktan sonra her 2 tran sorgusunu'da çalıştırıyoruz ve tran 2 sorgusunun t anında sorguyu getirmediğini, tran 1'in işlemini bitirdikten sonra geldiğini görüyoruz. Ayrıca tran 1 den dönen her iki sorgu sonucununda tutarlı bir şekilde geldiğine şahit oluyoruz.
 
Anahtar Not:  Repeatable read problemi, farklı tran sorgularından dönen delete ve update cümlelerinin ilgili satırı etkilemesiyle oluşur ancak phantom read problemi ise insert cümlesinin ilgili tran sorgusunu etkilemesiyle oluşur. Aralarındaki ince fark bu şekildedir..Dolayısıyla sizin ilgili transaction sorgusunda yazacağınız "serializable level" önlemi ile hem phantom read hem de repeatable read sorununu engelleyebileceğinizi ifade etmek isterim.
 
 

Trigger Serisinin İlk Bölümüne Buradan Ulaşabilirsin

Trigger'lar özelleşmiş bir tür stored prosedürlerdir(SP).