Bir Transaction önceki yazımda da bahsettiğim 4 ACID özelliğini sağlamalıdır. Bunlardan biri olan Isolation(İzolasyon) hakkında bu kısmında bahsedeceğim.
En üst düzeyde mükemmel bir izolasyon, tüm eşzamanlı Transactionların birbirini etkilememesini sağlar
Bir Transaction’a, onunla aynı anda çalışan diğer Transactionlar tarafından müdahale edilmesinin birkaç yolu vardır. Bu yollara read phenomenon denilmektedir. Bunlarda 4 çeşittir.
Read Phenomena
Bir veritabanı düşük düzeyde bir işlem yalıtımında çalışıyorsa oluşabilecek bazı okuma olayları şunlardır:
- Birincisi, dirty read phenomenon. Bir transaction, henüz commit edilmemiş başka bir eşzamanlı transaction tarafından yazılan verileri okuduğunda olur. Bu çok kötü, çünkü diğer işlemin sonunda commit edilip edilmeyeceğini veya rollback yapılacağını bilmiyoruz. Bu nedenle, rollback olması durumunda yanlış veriler kullanabiliriz.
- Karşılaşabileceğimiz ikinci phenomenon ise non-repeatable read. Bir transaction aynı kaydı iki kez okuduğunda ilk okumada aldığı değeri transaction bitmeden bir daha okuman istediğinde farklı bir değer almasıdır. Sebebi o transaction çalışırken ilk okuma sonrası başka bir transaction o kayıt üzerinde işlem yapıp commit etmesidir.
- Phantom read, non-repeatable read’e benzer, ancak bir yerine birden çok satır arayan sorguları etkiler. Bu durumda, bir transctionda aynı sorgu tekrar çalıştırıldığında, ilk aramada aldığı satırlar, başka bir transaction tarafından yeni satırlar eklemek veya mevcut satırları silmek gibi işlemleri yapıp commit ettiklerinde, geçerli transaction’ın tekrar sorgusu farklı bir satır kümesi döndürme phanton read olarak adlandırılır.
- Bir grup transaction ayrılmasını içeren bir başka olgu da serialization anomaly’dir. Bir grup eşzamanlı commit edilmiş transactionın, herhangi bir sırayla, birbiri ile çakışmadan çalıştırmaya çalışırsak, sonucunu elde edemediğimiz zamandır.
Standart 4 Isolation Level (İzolasyon seviyesi)
Şimdi bu fenomenlerle başa çıkmak için, Amerikan Ulusal Standart Enstitüsü veya ANSI tarafından 4 standart izolasyon seviyesi tanımlandı.
- Read Uncommitted: En düşük izolasyon seviyesidir . Bu seviyedeki transactionlar, diğer commit edilmemiş transactionlar tarafından yazılan verileri görebilir ve böylece dirty read gerçekleşmesine izin verir .
- Read Committed : Bu ise, transactionların yalnızca diğer transactionları tarafından commit edilen verileri görebildiği yerdir. Bundan dolayı dirty read artık mümkün değil.
- Repeatable Read : İzolasyon seviyesi biraz daha katıdır . Aynı select sorgusunun, kaç kez yürütülürse yürütülsün, diğer bazı eşzamanlı transactionların sorguyu karşılayan yeni değişiklikler yapmış olsa bile her zaman aynı sonucu döndürmesini sağlar.
- Serializable : Son olarak en yüksek izolasyon seviyesi . Bu seviyede çalışan eşzamanlı işlemlerin (transactionın), üst üste binmeden birbiri ardına sırayla yürütülüyormuş gibi aynı sonucu vermesi garanti edilir. Temel olarak, bu eşzamanlı işlemleri sipariş etmenin en az 1 yolu olduğu anlamına gelir, böylece bunları tek tek çalıştırırsak nihai sonuç aynı olacaktır.
Postges’te İzolasyon seviyeleri
Bu işlemi de yine DBevear da 2 ayrı sekmede 2 farklı transaction üzerinden anlatacağım. Öncelikle postgresde default İzolasyon seviyesine bakalım. Bunun için aşağıdaki sorgu ifadesini kullanalım.
Postges’te İzolasyon Seviyeleri Değiştirme
MySql gibi bazı veri tabanlarında izolasyon seviyeleri oturum bazlı ayarlanırken Postgres’te bu iş transaction özgü olarak transaction başlamadan önce ayarlanabilmektedir.
Şimdi o 1.transactionda Hesapları sorgu ile görelim
Sonra 2.transaction’ı başlatıp hesap 1 için sorgu atalım
Ardından tx1 e dönelim ve hesap 1 in bakiyesini UPDATE edelim
Hesap 1'in bakiyesi burada 40 dolar olarak değiştirilmiştir. Şimdi o hesabı tx2
de tekrar sorguluyoruz
Garip bir durum hala 50 tl. Ama biz read-uncommited seviyesinde işlem yapıyoruz, bu yüzden tx2, tx1 deki uncommited datayı görmesi gerekmez miydi?
Aslında, Postgres’in dokümanlarına bakarsak, Postgres’in read-uncommitted,
read-committed
ile tamamen aynı şekilde davrandığını görebiliriz. Yani temelde, Postgres'in sadece 3 izolasyon seviyesine sahip olduğunu ve en düşük seviyenin read committed olduğunu bu örnek ile görmüş oluyoruz
. Mantıklı çünkü normalde read-uncommitted
hiçbir koşulda kullanmak istemeyiz .
Kaldığımız yerden devam edelim ve tx1’i commit edelim. Ardından tx2 de
bir kez daha hesap 1'i sorgulayalım.
Beklendiği gibi 40 TL olarak bakiyemiz geldi. Şimdi bunu da (tx2) commit edip sonraki izolasyon seviyesine geçelim. Commit ten sonra tranaction bittiği için yeni transaction default izolasyon seviyesinde (read committed olarak) başlar unutmayalım.
1.Read Commited İzolasyon Seviyesi
Şimdi 2 yeni tx başlatıp izolasyon seviyerlerini default olarak bırakacağım.
Şimdi daha önce olduğu gibi, tx1 de
tüm hesapları seçelim , ardından tx2 de
sadece hesap 1'i seçelim .
Dirty read phenomenon yanı sıra, phantom read nasıl ele aldığını da görmek istiyoruz, bu yüzden bakiyenin 30 dolardan büyük veya ona eşit olduğu tüm hesapları bulalım. Şu anda, 3 kayıt da bu arama koşulunu karşılamaktadır.
Şimdi tx1 e
geri dönelim hesap 2'in bakiyesinden 10 TL çıkaralım .
Tx2 Hesap 2'i seçersek, Tx1’de henüz COMMİT edilmediğinden hala 30 TL olacaktır. Yani read-committed izolasyon seviyesinde dirty read mümkün değildir. Ama tx1 geri dönüp commit eder isek o zaman ne olur bakalım?
Beklendiği gibi tx2de hesap2 in bakiyesini 20 TL olarak gördük Şimdi bakiseyi 310 Tl den büyük olanları sorgulayalım. Bu kez biz önceki sorguda aldığımız 3 kayıt yerine 2 kayıt alacağız.
Güncellenen hesap 2'in bakiyesi artık arama koşulunu karşılamıyor, bu nedenle sonuç kümesinden kayboldu. Yani read-committed izolasyon düzeyinde phantom read meydana geldi. Bu, MySQL’dekiyle aynı davranıştır. Şimdi bu işlemide commit edip sonraki seviyeye geçelim.
2.Repeatable Read İzolasyon Seviyesi
Şimdi 2 yeni tx başlatıp izolasyon seviyerlerini repeatable read olarak ayarlayacağım.
Yukardaki aynı işlemleri tekrar yapalım. Tx1 de tüm hesapları sorgulayalım. Tx2de hesap2 alıp ardından bakiyesi 20 TL den büyük olanları sorgulayalım
Şimdi tx1 e
geri dönelim hesap 2'in bakiyesinden 10 TL çıkaralım .
Bu işlemde bakiye 10 TL olarak güncellendi. Commit edelim ve tx2 de ne olacağını görelim.
Tx2 e hesap2 için sorgu attığımızda hala önceki değeri 20 TL yi getirdiğiniz görüyoruz, tx1 commit etmesine ve tx1 de değişmiş olmasına rağmen. Bunun sebebi bu izolasyon seviyesidir. Aynı select sorgusu daima aynı sonucu getirir. Non-repeatable read phenomenon bu izolasyon seviyesinde bu sebeple oluşmaz. Ayrıca tx2 daha önce sorguladığımız bakiyesi 20 TL den büyül eşit sorgusunu tekrar çağırırsak sonuç yine aynı dönecektir. Burada phantom read phenomenonun önlenmiş olduğunu da gördük.
Şimdi de tx2 de işleme devam edip hesap2 nin bakiyesini update etmeye çalışacağım.
Eğer bu işlemi MySQL de yapsak repeatable-read izolasyon seviyesinde, bakiyenin 0 TL olarak güncellenmesine izin verdiğini görürdük. Ama burada, Postgres’te bir hata aldık
“HATA: eşzamanlı güncelleme nedeniyle erişim serileştirilemedi “
Bunun gibi bir hatayı almak, bakiyenin değiştirilmesine izin vermekten çok daha iyidir (rollback ister), çünkü işlemin 20 TL’den 10 TL’i çıkarırken 0 TL ürettiğini görmek kafa karıştırıcı bir durumdur. İyi varsın Postgres J
Postgres’te Serialization anomaly
Şimdiye kadar 3 tür phenomenon ile karşılaştık: dirty read, non-repeatable read ve phantom read. Ama serialization anomaly ile henüz karşılaşmadık. Bakalım bu sefer nasıl görünecek. 2 yeni tx başlatalım ve izolasyon seviyelerini repeatable-read olarak ayarlayalım.
Ardında Tx1 de tüm hesapları sorgulayalım sonra Tx1 de tüm hesapların bakiyesinin SUM edip yeni bir hesap oluşturup ona bakiye olarak verelim ve önce tx1de ardından da tx2de tüm kayıtları sorgulayalım
Şimdi bunun yeni kaydını tx1 de görebiliriz. Ancak tx2 de bu işlemi yapmak isterse ne olur ? Repeatable-read izolasyon düzeyini kullandığımızdan tx2deki select sorgusunda yeni kaydın henüz eklemiş olduğu orijinal hesap listesini görecektir. Ve tx2 de SUM alırsak önceki SUM gelecektir.
Ardından tx2 de de SUM için yeni bir kayıt atalım. Ardından tx1 ve tx2 yi commit edelim
Her ikisi de veri tabanına başarıyla işlendi. Ve 140 dolarlık aynı bakiyeye sahip 2 mükerrer sum kaydı var. Bu bir serileştirme anomalisidir.
Niye?
Çünkü bu 2 işlem birbiri ardına seri olarak çalıştırılırsa, aynı toplamı 140 TL olan 2 kaydımız olması mümkün değildir. Önce TX1 veya TX2nin çalışması fark etmez, ilk kaydımız 140 TL ve diğer kaydımız 280 TL olmalıdır.
Tamam serialization anomaly, repeatable-read izolasyon seviyesinde böyle oluyor.
Şimdi en üst seviyeyi yani serializable de bu işlemi deneyelim
3.Serializable İzolasyon Seviyesi
Bu anomalinin durdurulup durdurulamayacağını görmek için. Öncelikler tablodaki sum kayıtlarını silip hesap 2 nin bakiyesini 60 TL yaptım. Sonra 2 yeni transaction açıp izloasyon seviyelerini serializable yaptım.
Ardından önce tx1 de tümünü sorgulayıp SUM bulup yeni kayıt olarak Insert edelim . sonra Aynısını tx2 için yapalım. İşlemler sonra son görüntü aşağıdaki gibidir.
Ardından sırasıyla ikisinide commit edelim.
Görüldüğü üzere tx1 başarılı şekilde commit edilirken, tx2 commit edilirken hata meydana geldi.
Ve Postgres bize, yeniden denersek transactionın başarılı olabileceğine dair bir ipucu veriyor.
Yani bu iyi!
Serializable anomaly tam olarak engellenmiş oldu.. 2 eşzamanlı transaction artık daha önce olduğu gibi yinelenen kayıtlar oluşturmuyor.
Postgres’in potansiyel read phenomenları tespit etmek ve bir hata vererek onları durdurmak için bir dependencies detection (bağımlılık tespit) mekanizması kullandığı sonucuna varabiliriz.
Özetleyecek olur isek POSTGRES’te
Postgres’teki izolasyon seviyeleri oldukça benzer sonuçlar veriyor. Ancak, hala bazı önemli farklılıklar var. İlk olarak, read uncommitted izolasyon seviyesi read committed ile aynı şekilde davranır. Bu sebeble Postgres 4 yerine 3 izolasyon seviyesine sahiptir. MySqlden örnek vermek gerekirse 4 seviyede mevcuttur.
İkincisi, Postgres, MySQL gibi locking mechanism (kilitleme mekanizması) kullanmaz, ancak non-repeatable read (tekrarlanamayan okumayı), tutarsız eşzamanlı güncellemeleri ve serialization anomaly (serileştirme anormalliğini) durdurmak için daha iyi bir dependencies detection (bağımlılık algılama) tekniği kullanır.
Ayrıca Postgres’teki varsayılan izolasyon seviyesi read-committed iken, MySQL’de repeatable read’dir
Yüksek izolasyon seviyesi kullanırken aklınızda bulundurmanız gereken en önemli şey, bazı hatalar, zaman aşımı ve hatta deadlocks(kilitlenmeler) olabileceğidir. Bu nedenle, transactionlarımız için dikkatli bir şekilde retry mechanism (yeniden deneme) mekanizması uygulamalıyız.
Ayrıca, her veritabanı motoru izolasyon düzeyini farklı şekilde uygulayabilir. Bu nedenle, belgelerini dikkatlice okuduğunuzdan ve kodlamaya geçmeden önce kendi başınıza IDE ya da Terminal de denediğinizden emin olun. Burada MySql ile örnek vermemin sebebi o’dur.
Referanslar;