Transaction Yönetimi
Temel olarak bir ve ya birden çok veritabanı işleminden(CRUD sorgularından) oluşan tek bir iş birimidir.
Transactionın amacı üzerinde çalışılan iş birimde, çalıştırılan tüm işlemlerin eğer birinde hata meydana gelmesi durumunda, tümünü iptal etmek ya da tüm iş birimi çalıştırıldığında, hata oluşmamış ise başarılı bir şekilde veritabanına işlemek için kullanılır.
2. Neden Transactiona İhtiyaç Duyarız?
2 ana sebebi var:
- Sistemin arıza durumunda bile çalışma birimimizin güvenilir ve tutarlı olmasını istiyoruz.
- Veritabanına aynı anda erişen programlar arasında izolasyon sağlamak için
Yukarıda bahsedilen 2 hedefe ulaşmak için, bir veritabanı işleminin ACID özelliklerini karşılaması gerekir.
3. ACID Nedir ?
ACID, hatalara, elektrik kesintilerine ve diğer aksaklıklara rağmen veri geçerliliğini garanti etmeyi amaçlayan veritabanı işlemlerinin(transactionlarının) bir dizi özelliğidir. Veritabanları bağlamında, ACID özelliklerini karşılayan bir dizi veritabanı işlemi transaction olarak adlandırılır.
Bu özellikler :
• A Atomicity’dir; Bu, bir transactionda tüm sorgu(CRUD) işlemlerinin başarıyla çalışıp veritabanında işlenmesi(commit edilmesi) ya da tüm işlemin başarısız olduğu (bir veya birden fazla sebebden dolayı) ve her şeyin geri alındığı (rollback edilmesi) yani işlemin veritabanında değişmemesi anlamına gelir.
• C Consistency(Tutarlılıktır); Bu ise bir transaction yürütüldükten(execute) sonra, veritabanına yazılan tüm veriler önceden tanımlanmış kurallara göre geçerli olmalıdır. Ör: kısıtlamalar(constraints), basamaklar(cascades) ve tetikleyiciler(triggers) vs.
• I Isolation(İzolasyon); Bu aynı anda çalışan transactionlar birbirini etkilememelidir. Bir transaction tarafından yapılan değişikliklerin ne zaman başka transactionlar tarafından görülebileceğini tanımlayan birkaç yalıtım(izolation) düzeyi vardır. Diğer yazımda bunun hakkında daha fazla bilgi vereceğim.
• D Durability(Dayanıklılık); Temel olarak, başarılı bir transaction tarafından yazılan tüm verilerin kalıcı bir depolamada(persistent storage) kalması ve bir sistem arızası olsa bile bu verinin kaybolmaması gerektiği anlamına gelir.
4. Bir Transaction Nasıl Çalışır ?
Oldukça basit:
- BEGIN ya da START TRANSACTION deyimi ile bir işlem başlatıyoruz. “Başlatma ifadesi çalışılan IDE ve Terminale göre değişiyor.”
- Sonra bir dizi normal SQL sorgusu (CRUD veya CRUD ile bir arada yazılımsal işlem) yaparız.
- Hepsi başarılı olursa, işlemi kalıcı hale getirmek için COMMIT ederiz, veritabanında durum değiştirilir yani yapılan işlemler veritabınına yansıtılır.
Aksi takdirde hata var ise, yani herhangi bir sorgu ifadesi başarısız olursa, işlemi ROLLBACK yaparız, böylece işlemin önceki sorgularında yapılan tüm değişiklikler gitmiş olacak ve veritabanı işlemden önceki haliyle, aynı şekilde kalacaktır.
Artık, veritabanı transactionları hakkında temel bilgilere sahibiz. Artık örnekler ile transaction olayını pekiştirelim.
Örneğin, bir banka da hesaplar arasında bir havale işlemi yapıldığını varsayalım.Ve Hesap 1'den Hesap 2'ye 10 TL aktarmak istiyoruz.
Bu işlem normal şartlarda yapılmak istenirse aşağıdaki 5 adımdan oluşur:
1. Tutarı 10 TL olan bir transfer kaydı oluşturuyoruz.
2. Hesap 1 için -10 TL’ye eşit bir hareket(çıkış) kaydı oluşturuyoruz, para bu hesaptan çıktığı için
3. Hesap 2 için +10 TL’ye eşit bir hareket(giriş) kaydı oluşturuyoruz, çünkü para bu hesaba giriyor.
4. Daha sonra Hesap 1'in bankada bulunan bakiyesinden -10 TL çıkararak güncelleriz.
5. Ve son olarak, Hesap 2'nin bakiyesini ona +10 TL ekleyerek güncelliyoruz.
Bunu işlemi veritabanında test etmek için aşağıdaki tablolarımızı oluşturalım;
Tablolarımızı oluşturduktan sonra ilk olarak basit bir transaction oluşturarak onu çalıştıralım;
NOT: İşlemler için POSTGRESQL veritabanı ile birlikte DBeaver yani SQL istemcisi ve veritabanı yönetim aracını kullanacağım. İsterseniz DBeaver bu linkten indirebilirsiniz.
Transaction öncesi hesap tablosunda durum aşağıdaki gibidir;
Varsayılanda Auto-Commit durumunda olan transaction özelliğini aşağıdaki gibi Manual Commit olarak değiştiriyoruz.
Ardından transactionı başlatmak için start transaction; ifadesini çalıştırıyoruz ve Ahmet isimli hesap sahibinin bakiyesine 50 TL ekliyoruz. Dönüşte bize 150 TL olarak dönecek.
Ve yeşil alanda 2 olarak gözüken yere tıkladığımızda transaction logunu takip edebilirz. Şu an 2 adet işlem yapıldı. Önce transaction start edildi sonra bir update ifadesi çağrıldı.
Bu aşamada yeni bir sql sayfası açarak Ahmet isimli kişinin bakiyesini görüntüleyelim. Ve bize eski hali yani 100 olarak dönecektir.
Not: Yeni sekme açıldığında varsayılan yine Auto Commit olarak set edildi. Buna dikkat ediniz !!
Sebebi biz bu işlemi/transactionı COMMIT etmedik. Şimdi COMMIT edip tekrar çalıştırır isek Ahmet’in son bakiyesi bize 150 olarak dönecektir.
Bu örnek ile 2.2 de bahsettiğimiz veritabanına aynı anda erişen programlar arasında izolasyon sağlama görevini başarılı şekilde test ederek görmüş olduk. Şimdi biraz daha derin konulara girelim :)
5. LOCK nedir ? Nasıl oluşur?
LOCK dediğimiz şey veritabanında bir kaynağı 2 farklı işlem kullanmak istediğinde, ilki işini bitirene kadar ikincisinin bloklanmasıdır.
Şimdi bu olayı örnek üzerinde göstereceğim.
Bunun için DBevear ile 2 adet sorgu tabı açıp ardından “START TRANSACTION” keyword ile paralel olarak transaction başlatacağım. Her bir adımı sırayla 2 sorgu tabında da uygulayacağım. 2 transactionda da SELECT, INSERT ifadelerinde sorun olmayacak. Fakat 1.transactionda UPDATE yapılıp 2.transactionda UPDATE işlemi çağrılır ise aşağıdaki gibi LOCK oluşacak.
Ta ki biz 1. işlemde COMMIT ya da ROLLBACK ifadesini çağrıncaya kadar 2.transactionda ki LOCK kaybolmayacak. Çünkü ikiside aynı satırı güncellemeye çalıştı. İlk başlayan İkinciyi işi bitene kadar blokladı.
Select…FOR UPDATE ile LOCK Nasıl Oluşur?
Select ile seçilen kayıt kümesine dışarıdan DML uygulanamamasını sağlar. Başka bir deyişle; commit veya rollback yapana kadar o select kayıt kümesine lock koyar. FOR UPDATE cümleciği select sorgusunun en sonuna yazılır
Share Lock nedir?
Yukarıda biz INSERT lerde LOCK olmaz diye düşünmüş olabiliriz ama öyle değil INSERT ile de LOCK oluşturabiliriz. Bunu yine bir örnek üzerinde anlatacağım.
Senaryomuz yine aynı fakat FOR UPDATE özelliğini kullarak o satıra gelen işlemleri Lock edeceğiz. Ama bunu yaparken akışa lütfen dikkat edelim.
Başlıyalım;
1. Öncelikle 1.tx başlatıyoruz ilk olarak Hesap1 den Hesap2 ye bir transfer kaydı oluşturalım. Ardından Hesap1 den -5 TL tutarında bir hareket kaydı oluşturalım.
2. Daha sonra 2.tx e geçelim ve orada da yine Hesap1 den Hesap2 ye bir transfer kaydı oluşturalım.
3. Sonra 1.tx ye geri gelelim ve Hesap2 ye +5 TL tutarında bir hareket kaydı oluşturalım. Ardından FOR UPDATE kısımlı Hesap1 için olan SELECT satırını çalıştıralım
4. Bu noktada bize bir lock oluşacak. Fakat biz buraya kadar hesap tablosunda hesap1 için bir update işlemi yapmadık bu nasıl oldu?
Bunun sebebi accounts tablosu transfers tablosunda FOREIGN KEY olarak 2 sutünda saklı ve dolaylı olarak o satırdaki işleme izin vermiyor takii ROLLBACK yada COMMIT edilene kadar.
Buradan devam edip deadlock oluşturalım.
6.Dead Lock nedir?
Bir işlem tarafından bloklanan başka bir işlemin, onu bloklayan işlemi bloklamasıdır. Biraz karmaşık gelebilir. Yukarıdaki örneğe devam ederek daha iyi anlayacağız. Yukarı da Share Lock kısmında Lock oluşan Transactiondan sonra işleme şu şekilde devam edilidiğinde deadlock oluşacak;
1. Diğer transactiona geri dönülüp entryler oluşturulup, select..for update yapılır ise deadlock oluşur. (Yani 6.adımdan sonraki kısımlar çalıştırılırsa SELECT de)
Sebebi 5.adımda bloklanan transaction, 6.adımda ki SELECT çağrıldığında onu bloklayan transactionı bloklamasıdır.
Deadlock Nasıl Düzeltilir ?
1. Tavsiye Edilmeyen Yöntem : Foreign keyleri kaldırarak
2. Tavsiye Edilen Yöntem : SELECT…FOR UPDATE yerine SELECT… FOR NO KEY UPDATE kullanarak
Şimdi görsel akışa bakalım;
Transaction’ı ne zaman ve nasıl etkili kullanabiliriz?
- Transactionın faaliyet alanını daraltın
- Kontrol sorgularını yani SELECT ifadelerini, transaction içinde kullanmayın. (SELECT…FOR UPDATE vs. sorgular hariç)
- Transactionda kullanılacak olan bilgileri transactionda önce alın.
- Transaction içinde alternatif bir yöntem ile yapılabilecek işlemleri transactionın dışına taşıyın. (Mail, SMS gönderim gibi)
- Transaction için en uygun izolasyon seviyesini ayarlayın.
Son olarak;
Transaction ne kadar küçük olursa sisteme ve connection poola binen yük o kadar az olur. Yani bir connection açıp 100 sorguluk bir ifade göndermek yerine, küçük küçük parçalar halinde daha fazla connectionla gitmek hem sistemi rahatlatır hemde IDLE IN TRANSACTION lar oluşmaz.
Sonra ki yazımda izolasyon seviyelerinden(Isolation Levels) bahsedeceğim.