Selamlar,
Veritabanı sistemlerinde, birden fazla kullanıcının veya işlemin aynı anda aynı verilere erişmesi durumunda, veri tutarlılığı ve bütünlüğünü korumak için kilit mekanizmaları kullanılır. Kilit mekanizmaları, veriye erişimi sınırlayarak veya koordine ederek, veri kaybı, bozulma veya çakışma gibi sorunları önlemeye çalışır. Bu yazıda, kilit mekanizmalarının iki temel türü olan optimistic ve pesimistic lock kavramlarını, çalışma biçimlerini, avantaj ve dezavantajlarını ve kullanım senaryolarını inceleyeceğiz.
Optimistic Lock
Optimistic lock, veriye erişimde çakışma olasılığının düşük olduğu varsayımına dayanan bir kilit mekanizmasıdır. Bu mekanizmada, veriye erişen işlem veya kullanıcı, veriyi okuduğu anda kilitlenmez. Veriyi güncellemek istediğinde, verinin başka bir işlem veya kullanıcı tarafından değiştirilip değiştirilmediğini kontrol eder. Eğer veri değiştirilmemişse, güncelleme işlemi gerçekleştirilir. Eğer veri değiştirilmişse, güncelleme işlemi reddedilir ve işlem veya kullanıcıya bir hata mesajı döndürülür. Bu durumda, işlem veya kullanıcı, veriyi tekrar okuyup güncellemeyi yeniden deneyebilir veya vazgeçebilir.
Optimistic lock mekanizmasının avantajı, veriye erişimde kilitlenme olmadığı için performansın ve ölçeklenebilirliğin artmasıdır. Ayrıca, kilitlenme nedeniyle oluşabilecek deadlock gibi sorunlar da ortadan kalkar. Optimistic lock mekanizmasının dezavantajı ise, veriye erişimde çakışma olasılığının yüksek olduğu durumlarda, güncelleme işlemlerinin sık sık reddedilmesi ve işlem veya kullanıcının veriyi tekrar okuyup güncellemeyi yeniden denemek zorunda kalmasıdır. Bu da performansı ve verimliliği düşürebilir.
Optimistic lock mekanizmasını uygulamak için, veritabanında verinin sürümünü veya zaman damgasını (timestamp) tutan bir alan olması gerekir. Bu alan, veri her güncellendiğinde artırılır veya güncellenir. Veriyi güncellemek isteyen işlem veya kullanıcı, veriyi okuduğu anda bu alanın değerini de alır. Güncelleme yaparken, bu alanın değerini veritabanındaki değerle karşılaştırır. Eğer değerler aynıysa, veri değiştirilmemiştir ve güncelleme yapılabilir. Eğer değerler farklıysa, veri değiştirilmiştir ve güncelleme reddedilir.
Optimistic lock mekanizması, veriye erişimde çakışma olasılığının düşük olduğu, verinin sıklıkla okunduğu ancak nadiren güncellendiği, verinin güncellenmesinin kritik olmadığı veya veri kaybının tolere edilebilir olduğu durumlarda kullanılabilir. Örneğin, bir blog sitesinde, blog yazılarının okunması çok daha sık olurken, güncellenmesi çok daha nadir olabilir. Bu durumda, optimistic lock mekanizması, performansı artırmak için tercih edilebilir. Ancak, blog yazılarının güncellenmesi çok önemli ise veya veri kaybı kabul edilemez ise, pesimistic lock mekanizması daha uygun olabilir.
Optimistic lock mekanizmasının bir örneği aşağıda kod üzerinden verilmiştir. Bu örnekte, blog yazılarını tutan bir Posts tablosu ve bu tabloda her yazının sürümünü tutan bir version alanı olduğunu varsayalım. Ayrıca, blog yazılarını güncelleyen bir UpdatePost metodumuz olsun. Bu metod, veriyi okurken Version alanının değerini de alır ve güncelleme yaparken bu değeri veritabanındaki değerle karşılaştırır. Eğer değerler aynıysa, güncelleme yapılır ve version alanı bir artırılır. Eğer değerler farklıysa, güncelleme reddedilir ve bir hata mesajı döndürülür.
public void UpdatePost(int postId, string newContent)
{
var post = db.Posts.FirstOrDefault(p => p.Id == postId);
var oldVersion = post.Version;
post.Content = newContent;
var result = db.Database.ExecuteSqlCommand(
"UPDATE Posts SET Content = @p0, Version = Version + 1 WHERE Id = @p1 AND Version = @p2",
post.Content, post.Id, oldVersion);
if (result == 0)
{
throw new Exception("Veri başka bir işlem veya kullanıcı tarafından değiştirilmiştir. Güncelleme işlemi başarısız olmuştur.");
}
}
Bu örnekte, optimistic lock mekanizması kullanılmadan önce, veriye erişimde çakışma olması durumunda, veri kaybı veya bozulma gibi sorunlar yaşanabilirdi. Örneğin, iki kullanıcı aynı anda aynı blog yazısını güncellemek istese, son güncelleyen kullanıcının değişiklikleri ilk güncelleyen kullanıcının değişikliklerini ezebilir ve veri kaybına neden olabilirdi. Optimistic lock mekanizması kullanıldıktan sonra, veriye erişimde çakışma olması durumunda, veri kaybı veya bozulma gibi sorunlar önlenmiş oldu. Örneğin, iki kullanıcı aynı anda aynı blog yazısını güncellemek istese, ilk güncelleyen kullanıcının değişiklikleri başarılı olurken, son güncelleyen kullanıcının değişiklikleri reddedilir ve kullanıcıya bir hata mesajı döndürülür. Bu şekilde, veri tutarlılığı ve bütünlüğü korunmuş olur.
Yukarıdaki örnekte optmistic lock için kurulan yapı manuel yapılmıştır yani tabloya bir version isminde alan eklenmiş ardından güncelleme işlemi sırasında bu değer okunmuş son olarak güncelleme işlemi ile bu version değeri 1 arttırılmıştır. Bu işlem manuel yapılmak yerine entity framework un bize sağladı imkanlar ilede yapılabilir. Entity Framework, optimistic lock için iki yöntem sunar bunlar Concurrency Token ve Row Version kavramlarıdır.
Concurrency Token, veri kaydının bir veya daha fazla kolonunu, veri değişikliğini izlemek için kullanan bir yöntemdir. Bu yöntemde, veri kaydı güncellenirken, concurrency token değiştirilir. Eğer başka bir işlem, concurrency token değişmeden önce veri kaydını güncellemeye çalışırsa, bir veri çakışması hatası alır. ConcurrencyToken, tablodaki herhangi bir sütunun özelliği olabilir.
Concurrency Token için örnek kullanım aşağıda verilmiştir.
public class Person
{
public int PersonId { get; set; }
[ConcurrencyCheck]
public string FirstName { get; set; }
public string LastName { get; set; }
}
ConcurrenyToken tanımı yukarıdaki gibi ConcurrencyCheck attribute ile yapılabildiği gibi aşağıdaki gibi fluent configuration yöntemi ile de yapılabilmektedir.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().Property(p => p.FirstName).IsConcurrencyToken();
}
var person = context.Person.Single(b => b.LastName == "Doe");
person.FirstName = "Paul";
context.SaveChanges();
UPDATE [dbo].[Person] SET [FirstName] = @0 WHERE (([PersonId] = @1) AND ([FirstName] = @2))
Burada, WHERE koşulunda hem PersonId hem de FirstName kullanılmıştır. Eğer başka bir işlem, FirstName değerini değiştirmişse, bu işlem başarısız olur ve EF Core bir DbUpdateConcurrencyException hatası fırlatır.
Bir diğer yöntem ise RowVersion dır. Row Version yöntemi, veri kaydının bir kolonunu, veri değişikliğini izlemek için kullanan bir yöntemdir. Concurrency Token da tablodaki herhangi bir alan özelinde ilerliyorduk row version da ise tabloya Version isminde yeni bir alan ekliyoruz ve doğrudan tablodaki bir sütunun değerini kontrol etmek yerine satır bazında kontrol yapıyoruz. Bu yöntemde, veri kaydı güncellenirken, row version değiştirilir. Eğer başka bir işlem, row version değişmeden önce veri kaydını güncellemeye çalışırsa, bir veri çakışması hatası alır. Yani Row Version tabloya yeni bir sütun ekleyerek, her satırın güncellik durumunu takip eder. Örneğin, aşağıdaki kodda, Person sınıfının Version özelliği bir row version olarak ayarlanmıştır.
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[Timestamp]
public byte[] Version { get; set; }
}
Fluent configuration ile ise aşağıdaki gibidir.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().Property(p => p.Version).IsRowVersion();
}
Bu yöntemle gerçekleşen update işlemi ve sonrası oluşan sql ifadesi aşağıda verilmiştir.
var person = context.Person.Single(b => b.LastName == "Doe");
person.FirstName = "Paul";
context.SaveChanges();
UPDATE [Person] SET [FirstName] = @p0 WHERE [PersonId] = @p1 AND [Version] = @p2;
Pesimistic Lock
Pesimistic lock, veriye erişimde çakışma olasılığının yüksek olduğu varsayımına dayanan bir kilit mekanizmasıdır. Bu mekanizmada, veriye erişen işlem veya kullanıcı, veriyi okuduğu anda kilitler. Veriyi güncellemek istediğinde, verinin başka bir işlem veya kullanıcı tarafından değiştirilmediğinden emin olur. Veriyi güncelledikten sonra, kilidi açar ve veriyi başka işlem veya kullanıcılara bırakır.
Pesimistic lock mekanizmasının avantajı, veriye erişimde çakışma olasılığının yüksek olduğu durumlarda, güncelleme işlemlerinin başarılı olması ve veri kaybı veya bozulma gibi sorunların önlenmesidir. Pesimistic lock mekanizmasının dezavantajı ise, veriye erişimde kilitlenme olduğu için performansın ve ölçeklenebilirliğin düşebilmesidir.
Pesimistic lock mekanizması, veriye erişimde çakışma olasılığının yüksek olduğu, verinin sıklıkla güncellendiği, verinin güncellenmesinin kritik olduğu veya veri kaybının tolere edilemez olduğu durumlarda kullanılabilir. Örneğin, bir banka uygulamasında, hesap bakiyelerinin güncellenmesi çok sık olurken, güncellenmesi çok önemlidir. Bu durumda, pesimistic lock mekanizması, veri kaybı veya bozulma riskini azaltmak için tercih edilebilir. Ancak, hesap bakiyelerinin güncellenmesi çok nadir ise veya veri kaybı kabul edilebilir ise, optimistic lock mekanizması daha uygun olabilir.
Pesimistic lock mekanizması kullanılmadan önce, veriye erişimde çakışma olması durumunda, veri kaybı veya bozulma gibi sorunlar yaşanabilirdi. Örneğin, iki işlem aynı anda aynı hesabın bakiyesini güncellemek istese, son güncelleyen işlemin değişiklikleri ilk güncelleyen işlemin değişikliklerini ezebilir ve veri kaybına neden olabilirdi. Pesimistic lock mekanizması kullanıldıktan sonra, veriye erişimde çakışma olması durumunda, veri kaybı veya bozulma gibi sorunlar önlenmiş oldu. Örneğin, iki işlem aynı anda aynı hesabın bakiyesini güncellemek istese, ilk güncelleyen işlem veriyi kilitlerken, son güncelleyen işlem verinin kilidinin açılmasını bekler. Bu şekilde, veri tutarlılığı ve bütünlüğü korunmuş olur.
Pesimistic lock işleminin uygulanabileceği farklı yöntemler vardır. Örneğin veritabanında verinin kilit durumunu tutan bir alan eklenebilir. Bu alan, veri kilitlendiğinde true, kilidi açıldığında false değerini alır. Veriyi güncellemek isteyen işlem veya kullanıcı, veriyi okurken bu alanın değerini de kontrol eder. Eğer değer false ise, veri kilitlenmemiştir ve işlem veya kullanıcı veriyi kilitler. Eğer değer true ise, veri kilitlenmiştir ve işlem veya kullanıcı verinin kilidinin açılmasını bekler. Veriyi güncelledikten sonra, işlem veya kullanıcı verinin kilidini açar ve veriyi başka işlem veya kullanıcılara bırakır. Fakat bu yöntem çok tercih edilebilen bir yöntem değildir. Örneğin, locked alanı true olan bir kaydın, işlemi tamamlamayan veya hata veren bir kullanıcı tarafından kilitli kalması durumunda, diğer kullanıcılar bu kayda erişemez. Bu durum, performansı ve verimliliği düşürebilir. Ayrıca, locked alanı false olarak güncellemeyi unutmak veya atlamak da veri bütünlüğü sorunlarına yol açabilir.
Pesimistic lock uygulamalarında en çok tercih edilen yöntem veritabanı özelinde kullanılan lock keyword leridir. Mesela SQL Server bu amaca hizmet eden keywordlerden biri XLOCK’dır. Yapılacak işlemlerde XLOCK ile veritabanında ilgili veriyi kilitleyip çakışmanın önüne geçebilmekteyiz.
Pessimistic lock, veritabanı seviyesindeki bir özelliktir bu yüzden Entity Framework, pessimistic lock desteği sunmaz. Bunun nedeni, Entity Framework’ün veritabanı bağımsız bir ORM çözümü olmasıdır. Entity Framework, farklı veritabanları ile çalışabilen genel bir arayüz sağlar. Ancak, farklı veritabanları, pessimistic lock için farklı yöntemler kullanabilir. Örneğin, SQL Server, WITH (XLOCK) veya WITH (UPDLOCK) gibi yöntemler sunar. Oracle, FOR UPDATE veya FOR UPDATE NOWAIT gibi yöntemler sunar PostgreSQL, FOR UPDATE veya FOR SHARE gibi yöntemler sunar. Bu farklılıklar, Entity Framework’ün pessimistic lock için standart bir yöntem sunmasını zorlaştırır.
Entity Framework, pessimistic lock desteği sunmaz, ancak pessimistic lock kullanmak isteyen uygulamalar için bir çözüm sunar. Bu çözüm, Entity Framework’ün SQL komutlarını doğrudan çalıştırmasına izin veren FromSqlRaw veya ExecuteSqlRaw gibi yöntemlerdir. Bu yöntemler, veritabanı seviyesindeki kilit mekanizmalarını kullanarak pessimistic lock uygulamak için kullanılabilir. Örneğin, SQL Server için aşağıdaki kod örneği, bir veri kaydını pessimistic lock ile okumak için kullanılabilir.
var blog = context.Blogs.FromSqlRaw("SELECT * FROM Blogs WITH (XLOCK) WHERE Id = 1").Single();
Bu yöntem, Entity Framework’ün pessimistic lock desteği sunmamasının bir alternatifidir. Ancak, bu yöntem, veritabanı bağımlı olduğu için, veritabanı değiştirildiğinde kodu da değiştirmek gerekir
Bu yazıda, veritabanı sistemlerinde veri tutarlılığı ve bütünlüğünü korumak için kullanılan kilit mekanizmalarının iki temel türü olan optimistic ve pesimistic lock kavramlarını, çalışma biçimlerini, avantaj ve dezavantajlarını ve kullanım senaryolarını inceledik. Optimistic lock mekanizması, veriye erişimde çakışma olasılığının düşük olduğu, verinin sıklıkla okunduğu ancak nadiren güncellendiği, verinin güncellenmesinin kritik olmadığı veya veri kaybının tolere edilebilir olduğu durumlarda performansı artırmak için kullanılabilir. Pesimistic lock mekanizması, veriye erişimde çakışma olasılığının yüksek olduğu, verinin sıklıkla güncellendiği, verinin güncellenmesinin kritik olduğu veya veri kaybının tolere edilemez olduğu durumlarda veri kaybı veya bozulma riskini azaltmak için kullanılabilir. Her iki mekanizmanın da kendi senaryolarına uygun olarak kullanılması, veritabanı sistemlerinin daha verimli ve güvenli çalışmasını sağlayabilir.
İyi çalışmalar.