Isolation Level, bir transaction’ın bir işlemi karşısında diğer transaction’ların nasıl davranacağını belirler.
Öncelikle veritabanında oluşabilecek concurrency sorunlarını ele alalım. Daha sonra bu sorunların hangi isolation Level’da nasıl ortaya çıktığını örneklerle inceleyeceğiz.
Dirty Read: Transaction select yaptığında, başka bir transaction tarafından yapılan update’in commit edilmemiş halini okur. Diğer transaction commit yerine rollback yaptığı takdirde, ilk transaction tarafından yapılan select aslında geçerli olmayan datayı okumuş olacaktır. Bu yüzden dirty read olarak adlandırılır.
Lost update: İki transaction’ın aynı veriyi okuması ve daha sonra bu okuduğu değeri yeni bir değere set etmesi sonucu oluşur. İlk yapılan update kaybolacaktır. Bu yüzden lost update olarak adlandırılır.
Non-repeatable read: Bir transactionda select yapıldıktan sonra, başka bir transaction’ın yapılan bu select sonucunu değiştiren bir update gerçekleştirmesiyle, ilk transactionda aynı select ikinci kez çağrıldığında farklı bir değer dönmesi sonucu oluşur.
Phantom read: Bir transactionda yapılan bir select’in birden fazla kez çalışmasıyla, farklı sayıda kayıt döndürmesi sonucu oluşur. Birinci transactionda select yapıldığını düşünelim. Başka bir transaction içersinde birinci transactionda yapılan select’in içerdiği aralığa bir kayıt ekleme veya bu aralıktan bir kayıt sime işlemi gerçekleşmesi sonucunda , ilk transactionda ikinci kez select yapıldığında döndürdüğü kayıt sayısı farklı olacaktır.
Şimdi Isolation Level’ları örnekler yaparak inceleyelim. Örnekleri AdventureWorks2012 veritabanı üzerinde yapacağız.
1) Read Uncommitted: Update edilen datayı commit edilmemiş hali ile okumaya izin verir. Bu Isolation Level’da bütün concurrency sorunları meydana gelebilir. Bunun yanı sıra performans olarak daha etkilidir. Sırayla bu Isolation Level’da meydana gelebilecek concurrency sorunlarını örnekler yaparak inceleyelim.
Dirty Read: Aşağıdaki sorguyu yeni bir query sayfası açıp çalıştıralım. Bu sorguda FirstName’i Abigail, LastName’i Jones ve BusinessEntityID’si 12038 olan kayıt üzerinde işlem yapacağız. Aşağıdaki örnekte bu kaydın FirstName’ini ‘DirtyReadsExample’ olarak set ediyoruz. Daha sonra 10 saniye bekliyoruz ve update işlemini rollback yapıyoruz. Ve son olarak kaydımızı gerekli filtrelerle çekiyoruz.
USE AdventureWorks2012; BEGIN TRANSACTION; UPDATE Person.Person SET FirstName = 'DirtyReadsExample' WHERE LastName = 'Jones' AND BusinessEntityID=12038; WAITFOR DELAY '00:00:10.000'; ROLLBACK TRANSACTION; SELECT FirstName ,LastName,BusinessEntityID FROM Person.Person WHERE LastName = 'Jones' AND BusinessEntityID=12038;
İkinci bir session açıp aşağıdaki scripti çalıştıralım.
USE AdventureWorks2012; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT FirstName ,LastName,BusinessEntityID FROM Person.Person WHERE LastName = 'Jones' AND BusinessEntityID=12038;
Aşağıda gördüğümüz gibi ilk sessionda yaptığımız update’i rollback yaptığımız için en son çalıştırılan select’te FirstName Abigail olarak geldi. İkinci session ise, ilk session’da yapılan update commit edilmese bile değişikliği aldı ve FirstName’i DirtyReadsExample olarak gördü.
Sorguların sonuna WITH(NOLOCK) hintini koyduğumuz takdirde, sorgularımızda Isolation Level’ı set etmemiş olsak bile Read Uncommitted olarak çalışacaktır.
Lost Update: Aşağıdaki sorguyu ilk sessionda çalıştıralım.
USE AdventureWorks2012; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; DECLARE @SafetyStockLevel int = 0 ,@Uplift int = 5; BEGIN TRAN; SELECT @SafetyStockLevel = SafetyStockLevel FROM Production.Product WHERE ProductID = 1; SET @SafetyStockLevel = @SafetyStockLevel + @Uplift; WAITFOR DELAY '00:00:10.000'; UPDATE Production.Product SET SafetyStockLevel = @SafetyStockLevel WHERE ProductID = 1; SELECT SafetyStockLevel FROM Production.Product WHERE ProductID = 1; COMMIT TRAN;
Aşağıdaki sorguyu hemen arkasından ikinci bir sessionda çalıştıralım.
USE AdventureWorks2012; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; DECLARE @SafetyStockLevel int = 0 ,@Uplift int = 100; BEGIN TRAN; SELECT @SafetyStockLevel = SafetyStockLevel FROM Production.Product WHERE ProductID = 1; SET @SafetyStockLevel = @SafetyStockLevel + @Uplift; UPDATE Production.Product SET SafetyStockLevel = @SafetyStockLevel WHERE ProductID = 1; SELECT SafetyStockLevel FROM Production.Product WHERE ProductID = 1; COMMIT TRAN;
Aşağıda da görüldüğü gibi ilk transaction’da ProductID’si 1 olan kolana ait değer önce @SafetyStockLevel değişkenine aktarılıyor. Daha sonra bu değer @Uplift değişkeni ile toplanıp 10 saniye bekleme işlemi başlatılıyor. İkinci transactionda bekleme işlemine kadar olan kısım aynen gerçekleştiriliyor. Ve hemen arkasından bekleme olmadan update gerçekleştiriliyor. Sonuç olarak ikinci transactionda 1100 değerinin döndüğünü görüyorsunuz. İlk transaction’da da bekleme sonrası update’i gerçekleştirelim. Bu sefer değerin 1005 olduğunu ve ikinci transaction daki 1100 değerinin kaybolduğunu görüyoruz. Bu örneği daha iyi anlayabilmek için iki kişinin ortak bir hesap kullandığını düşünelim. Ve hesapta 1000 TL para olsun.İkinci transactionda ikinci kişinin hesaba 100 TL yatırdığını farzedelim. Birinci transactionda birinci kişide 5 TL yatırmış olsun. Eğer bu örnekteki gibi sorun olursa ikinci kişinin yatırdığı 100 TL kaybolmuş olacaktır.
Non-repeatable read: İlk sessionda aşağıdaki scripti çalıştıralım.
USE AdventureWorks2012; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; BEGIN TRANSACTION; SELECT TOP 5 FirstName ,MiddleName ,LastName ,Suffix FROM Person.Person ORDER BY LastName; WAITFOR DELAY '00:00:10.000'; SELECT TOP 5 FirstName ,MiddleName ,LastName ,Suffix FROM Person.Person ORDER BY LastName; COMMIT TRANSACTION;
İkinci sessionda da aşağıdaki scripti çalıştıralım.
USE AdventureWorks2012; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; BEGIN TRANSACTION; UPDATE Person.Person SET Suffix = 'Junior' WHERE LastName = 'Abbas' AND FirstName = 'Syed'; COMMIT TRANSACTION;
Aşağıda da görüldüğü gibi ilk sessiondaki ilk select’te Suffix değerleri Null geliyor. İkinci sessionda FirstName’i Syed olan ve LastName’i Abbas olan kolon’un Suffix değeri Junior olarak set ediliyor. İlk sessionda 10 saniye beklemeden sonra aynı select tekrar çekiliyor ve gördüğünüz gibi farklı bir sonuç döndüyor.
Phantom Read: İlk sessionda aşağıdaki sorguyu çalıştıralım.
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; BEGIN TRANSACTION --T1:RowCount=2 SELECT * FROM [AdventureWorks2012].[HumanResources].[Department] where GroupName='Manufacturing' WAITFOR DELAY '00:00:10.000'; --T2:RowCount=3 --Bu arada select sonucu aldığımız verilerle bazı işlemler yapıyor olabiliriz. SELECT * FROM [AdventureWorks2012].[HumanResources].[Department] where GroupName='Manufacturing' COMMIT TRANSACTION
ikinci sessionda aşağıdaki sorguyu çalıştıralım.
USE [AdventureWorks2012] GO INSERT INTO [HumanResources].[Department] ([Name] ,[GroupName] ,[ModifiedDate]) VALUES ('PhantomReadExample' ,'Manufacturing' ,'2002-06-01 00:00:00.000') GO
Aşağıda da görüldüğü gibi birinci transactiondaki ilk select iki kayıt döndürürken ikinci select 3 kayıt döndürmektedir.
READ UNCOMMITTED Isolation Level’ını kullandığımızda gördüğümüz gibi bütün concurrency sorunları meydana gelebiliyor. Bunun yanı sıra selectler bekleme olmadan sonuç aldığı için performans anlamında bazı kazançlar sağlıyor. Eğer veri tutarlılığı çok önemli olmayan bir sistemde çalışıyorsanız bu Isolation Level’ı tercih edebilirsiniz.
2) Read Committed: SQL SERVER ‘da default Isolation Level’ dır. Read Uncommitted’tan farklı olarak, transaction içersinde update edilen data commit olmadan başka bir transaction bu datayı okuyamaz. Bu şekilde dirty read oluşumu engellenmiş olur. Diğer taraftan concurrency ve performans azalmış olacaktır. Lost Update, Non-repeatable read ve Phantom Read bu Isolation Level’da da Read Uncommitted’ daki gibi davranacaktır. Dirty Read örneğimizi bu Isolation Level’ı kullanarak tekrarlayalım.
Dirty Read: READ UNCOMMITTED’tan farklı olarak ikinci session’ı çalıştırdığımızda, birinci session bitene kadar bizi bekletecektir. Birinci session bittikten sonra ikinci session’da da işlem tamamlanacaktır ve sonuç olarak bize Abigail Jones 12038 şeklinde kirli olmayan data geri dönecektir.
3) Repeatable Read: Bu Isolation Level’ın amacı, bir transaction içersinde yapılan select’e ait sonucun transaction sonlanana kadar aynı kalmasını sağlamaktır. Repeatable Read ile çalışan bir transaction içersinde select sorgusu çekilmişse, bu data başka bir transaction tarafından güncellenemez. Bu Isolation Level’da, dirty read örneğimiz ve phantom read örneğimiz READ COMMITTED Isolation Level’daki gibi davranacaktır. Lost Update ve Non-repeatable read örneklerimizi sırasıyla tekrar uygulayalım.
Lost Update: Örneğimizi uyguladığımızda ikinci session’ın, ilk session’ın bitmesini beklediğini görüyoruz. İlk session update komutunu çalıştırdığında ise ikinci session’ın aşağıdaki hatayı aldığını görüyoruz. Repeatable Read Isolation Level’ının, ilk session’da yapılan select’in ikinci session’da değişmesini engelleyerek lost update’i engellediğini görüyoruz
Transaction (Process ID X) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
Non-Repeatable Read: Örneğimizi uyguladığımızda ikinci session’ın, ilk session’ın bitmesini beklediğini görüyoruz. İlk session commit olduğunda READ COMMITTED Isolation Level’dan farklı olarak iki select sonucunun da aynı olduğunu görüyoruz. REPEATABLE READ Isolation Level’ı transaction bitene kadar select’in başka bir transaction tarafından update edilmesine izin vermedi. Ama transaction bittikten sonra ikinci session’ın istediği değişikliği yaptığını görüyoruz.
4) Serializable: Bu Isolation Level’da bir transaction içersinde select yapılıyorsa, başka bir transaction içersinden, ilk transaction bitene kadar ilk transactiondaki select aralığına erişilemez. (Insert,Update,Delete) Ve bir transaction içersinde data modifikasyon işlemleri yapılıyorsa, diğer transactionlar işlem yapılan aralıkta read yapamaz. Bu Isolation Level’da Dirty Read, Lost Update ve Non-repeatable Read örneklerimizi tekrarladığımızda, Repeatable Read Isolation Level’ daki sonuçlarla aynı sonuçları alacağız. Fakat Phantom Read örneğimizi tekrarladığımızda bu sorunun ortadan kalkmış olduğunu göreceğiz.
Phantom Reads: Sorgularımızı SERIALIZABLE Isolation Level’da çalıştırdığımızda ilk sorgudan aşağıdaki gibi bir sonuç dönüyor. Gördüğünüz gibi bu Isolation Level’da ilk transaction bitene kadar ikinci transaction insert işlemini gerçekleştiremedi.
Serimizin ikinci makalesinde, row versioning bazlı çalışan Isolation Level’ları (RCSI ve SNAPSHOT) inceleyeceğiz. Bu Isolation Level’ların tanımlarını yapıp hangi Isolation Level’da hangi concurrency sorunu nasıl oluşuyor, row versioning bazlı çalışmalarının artıları ve eksileri nelerdir örnekler üzerinde göreceğiz.