Eşzamanlılık Çakışmalarını İşleme

İpucu

Bu makalenin örneğini GitHub'da görüntüleyebilirsiniz.

Çoğu senaryoda veritabanları birden çok uygulama örneği tarafından eşzamanlı olarak kullanılır ve her biri verilerde birbirinden bağımsız olarak değişiklik yapar. Aynı veriler aynı anda değiştirildiğinde, tutarsızlıklar ve veri bozulması oluşabilir; örneğin, iki istemci aynı satırda bir şekilde ilişkili olan farklı sütunları değiştirdiğinde. Bu sayfada, verilerinizin bu tür eşzamanlı değişiklikler karşısında tutarlı kalmasını sağlamaya yönelik mekanizmalar ele alınmaktadır.

İyimser eşzamanlılık

EF Core, eşzamanlılık çakışmalarının nispeten nadir olduğunu varsayan iyimser eşzamanlılık uygular. İyimser eşzamanlılık, verileri önden kilitleyen ve ancak sonra değiştirmeye devam eden kötümser yaklaşımların aksine kilit almaz, ancak veriler sorgulandıktan sonra değiştiğinde veri değişikliğinin başarısız olmasını düzenler. Bu eşzamanlılık hatası, büyük olasılıkla yeni verilerde işlemin tamamını yeniden deneyerek uygun şekilde ele alan uygulamaya bildirilir.

EF Core'da, bir özelliği eşzamanlılık belirteci olarak yapılandırarak iyimser eşzamanlılık uygulanır. Eşzamanlılık belirteci, diğer özellikler gibi bir varlık sorgulandığında yüklenir ve izlenir. Ardından, sırasında SaveChanges()bir güncelleştirme veya silme işlemi gerçekleştirildiğinde, veritabanındaki eşzamanlılık belirtecinin değeri EF Core tarafından okunan özgün değerle karşılaştırılır.

Bunun nasıl çalıştığını anlamak için SQL Server'da olduğumuzu varsayalım ve özel Version bir özelliğe sahip tipik bir Kişi varlık türü tanımlayalım:

public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    [Timestamp]
    public byte[] Version { get; set; }
}

SQL Server'da, satır her değiştirildiğinde veritabanında otomatik olarak değişen bir eşzamanlılık belirteci yapılandırılır (aşağıda daha fazla ayrıntı sağlanır). Bu yapılandırma uygulandığında, basit bir güncelleştirme işlemiyle neler olduğunu inceleyelim:

var person = context.People.Single(b => b.FirstName == "John");
person.FirstName = "Paul";
context.SaveChanges();
  1. İlk adımda, veritabanından bir Kişi yüklenir; bu, artık EF tarafından her zamanki gibi izlenen eşzamanlılık belirtecini ve diğer özellikleri içerir.
  2. Daha sonra Kişi örneği bir şekilde değiştirilir ve özelliği değiştirilir FirstName .
  3. Daha sonra EF Core'a değişikliği kalıcı hale getirteceğiz. Eşzamanlılık belirteci yapılandırıldığından EF Core veritabanına aşağıdaki SQL'i gönderir:
UPDATE [People] SET [FirstName] = @p0
WHERE [PersonId] = @p1 AND [Version] = @p2;

WHERE yan tümcesine PersonId ek olarak EF Core'un da için Version bir koşul eklediğini unutmayın; bunun yalnızca sorguladığımız andan itibaren sütun değişmediyse satırı Version değiştirdiğini unutmayın.

Normal ("iyimser") durumda, eşzamanlı güncelleştirme gerçekleşmez ve UPDATE başarıyla tamamlanıp satırı değiştirir; veritabanı, beklendiği gibi BIR satırın UPDATE'in etkilendiğini EF Core'a bildirir. Ancak, eşzamanlı bir güncelleştirme oluşursa, UPDATE sıfırın etkilendiği eşleşen satırları ve raporları bulamaz. Sonuç olarak, EF Core'lar SaveChanges() uygulamanın uygun şekilde yakalaması ve işlemesi gereken bir DbUpdateConcurrencyExceptionoluşturur. Bunu yapmak için kullanılan teknikler, eşzamanlılık çakışmalarını çözümleme altında aşağıda ayrıntılı olarak yer almaktadır.

Yukarıdaki örneklerde mevcut varlıklara yapılan güncelleştirmeler ele alınmıştı. EF, aynı anda değiştirilmiş bir satırı silmeye çalışırken de atarDbUpdateConcurrencyException. Ancak, varlıklar eklenirken bu özel durum hiçbir zaman atılır; aynı anahtara sahip satırlar ekleniyorsa veritabanı gerçekten benzersiz bir kısıtlama ihlaline neden olabilir, ancak bu durum sağlayıcıya özgü bir özel durumun değil, DbUpdateConcurrencyExceptionatılmasıyla sonuçlanır.

Yerel veritabanı tarafından oluşturulan eşzamanlılık belirteçleri

Yukarıdaki kodda, bir özelliği SQL Server rowversion sütununa [Timestamp] eşlemek için özniteliğini kullandık. rowversion Satır güncelleştirildiğinde otomatik olarak değiştiğinden, satırın tamamını koruyan en düşük çaba eşzamanlılık belirteci olarak çok kullanışlıdır. SQL Server rowversion sütununu eşzamanlılık belirteci olarak yapılandırma işlemi aşağıdaki gibi yapılı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; }
}

rowversion Yukarıda gösterilen tür SQL Server'a özgü bir özelliktir; otomatik olarak güncelleştirilen eşzamanlılık belirteci ayarlama ayrıntıları veritabanları arasında farklılık gösterir ve bazı veritabanları bunları hiç desteklemez (örneğin, SQLite). Ayrıntılı bilgi için sağlayıcı belgelerinize bakın.

Uygulama tarafından yönetilen eşzamanlılık belirteçleri

Veritabanının eşzamanlılık belirtecini otomatik olarak yönetmesi yerine uygulama kodunda yönetebilirsiniz. Bu, yerel otomatik güncelleştirme türü bulunmayan SQLite gibi veritabanlarında iyimser eşzamanlılık kullanılmasına olanak tanır. Ancak SQL Server'da bile, uygulama tarafından yönetilen eşzamanlılık belirteci tam olarak hangi sütun değişikliklerinin belirtecin yeniden üretilmesine neden olduğu üzerinde ayrıntılı denetim sağlayabilir. Örneğin, önbelleğe alınmış veya önemsiz bir değer içeren bir özelliğiniz olabilir ve eşzamanlılık çakışmasını tetikleyen bir özellikte değişiklik yapmak istemiyor olabilirsiniz.

Aşağıdaki, bir GUID özelliğini eşzamanlılık belirteci olacak şekilde yapılandırmaktadır:

public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }

    [ConcurrencyCheck]
    public Guid Version { get; set; }
}

Bu özellik veritabanı tarafından oluşturulmadığından, kalıcı değişiklikler olduğunda uygulamada atamanız gerekir:

var person = context.People.Single(b => b.FirstName == "John");
person.FirstName = "Paul";
person.Version = Guid.NewGuid();
context.SaveChanges();

Her zaman yeni bir GUID değeri atanmasını istiyorsanız, bunu bir SaveChanges kesme noktası aracılığıyla yapabilirsiniz. Ancak eşzamanlılık belirtecini el ile yönetmenin avantajlarından biri, gereksiz eşzamanlılık çakışmalarını önlemek için ne zaman yeniden oluşturulduğunu tam olarak denetleyebilmenizdir.

Eşzamanlılık çakışmalarını çözme

Eşzamanlılık belirtecinizin nasıl ayarlandıklarından bağımsız olarak, iyimser eşzamanlılık uygulamak için uygulamanızın eşzamanlılık çakışmasının oluştuğu ve DbUpdateConcurrencyException oluştuğu durumu düzgün bir şekilde işlemesi gerekir; buna eşzamanlılık çakışmasını çözümleme adı verilir.

Seçeneklerden biri, kullanıcıya çakışan değişiklikler nedeniyle güncelleştirmenin başarısız olduğunu bildirmektir; kullanıcı daha sonra yeni verileri yükleyip yeniden deneyebilir. Ya da uygulamanız otomatik bir güncelleştirme gerçekleştiriyorsa, verileri yeniden sorguladıktan sonra hemen döngü yapabilir ve yeniden deneyebilir.

Eşzamanlılık çakışmalarını çözmenin daha karmaşık bir yolu, bekleyen değişiklikleri veritabanındaki yeni değerlerle birleştirmektir. Hangi değerlerin birleştirileceğine ilişkin kesin ayrıntılar uygulamaya bağlıdır ve işlem her iki değer kümesinin de görüntülendiği bir kullanıcı arabirimi tarafından yönlendirilebilir.

Eşzamanlılık çakışmasını çözmeye yardımcı olacak üç değer kümesi vardır:

  • Geçerli değerler , uygulamanın veritabanına yazmaya çalıştığı değerlerdir.
  • Özgün değerler , herhangi bir düzenleme yapılmadan önce başlangıçta veritabanından alınan değerlerdir.
  • Veritabanı değerleri , şu anda veritabanında depolanan değerlerdir.

Eşzamanlılık çakışmalarını işlemeye ilişkin genel yaklaşım:

  1. sırasında SaveChangesyakalayınDbUpdateConcurrencyException.
  2. Etkilenen varlıklar için yeni bir değişiklik kümesi hazırlamak için kullanın DbUpdateConcurrencyException.Entries .
  3. Eşzamanlılık belirtecinin özgün değerlerini veritabanındaki geçerli değerleri yansıtacak şekilde yenileyin.
  4. Çakışma oluşana kadar işlemi yeniden deneyin.

Aşağıdaki örnekte Person.FirstName ve Person.LastName eşzamanlılık belirteçleri olarak ayarlanmıştır. Kaydedilecek değeri seçmek için uygulamaya özgü mantığı eklediğiniz konumda bir // TODO: açıklama vardır.

using var context = new PersonContext();
// Fetch a person from database and change phone number
var person = context.People.Single(p => p.PersonId == 1);
person.PhoneNumber = "555-555-5555";

// Change the person's name in the database to simulate a concurrency conflict
context.Database.ExecuteSqlRaw(
    "UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1");

var saved = false;
while (!saved)
{
    try
    {
        // Attempt to save changes to the database
        context.SaveChanges();
        saved = true;
    }
    catch (DbUpdateConcurrencyException ex)
    {
        foreach (var entry in ex.Entries)
        {
            if (entry.Entity is Person)
            {
                var proposedValues = entry.CurrentValues;
                var databaseValues = entry.GetDatabaseValues();

                foreach (var property in proposedValues.Properties)
                {
                    var proposedValue = proposedValues[property];
                    var databaseValue = databaseValues[property];

                    // TODO: decide which value should be written to database
                    // proposedValues[property] = <value to be saved>;
                }

                // Refresh original values to bypass next concurrency check
                entry.OriginalValues.SetValues(databaseValues);
            }
            else
            {
                throw new NotSupportedException(
                    "Don't know how to handle concurrency conflicts for "
                    + entry.Metadata.Name);
            }
        }
    }
}

Eşzamanlılık denetimi için yalıtım düzeylerini kullanma

Eşzamanlılık belirteçleri aracılığıyla iyimser eşzamanlılık, eşzamanlı değişiklikler karşısında verilerin tutarlı kalmasını sağlamanın tek yolu değildir.

Tutarlılığı sağlamaya yönelik mekanizmalardan biri, yinelenebilir okuma işlemi yalıtım düzeyidir. Çoğu veritabanında bu düzey, bir işlemin veritabanındaki verileri işlem başlatıldığında olduğu gibi görmesini ve sonraki eşzamanlı etkinliklerden etkilenmemesini garanti eder. Temel örneğimizi yukarıdan aldığımızda, bir şekilde güncelleştirmek için Person öğesini sorguladığımızda, veritabanı işlem tamamlanana kadar bu veritabanı satırıyla başka hiçbir işlemin karışmadığından emin olmalıdır. Veritabanı uygulamanıza bağlı olarak, bu iki yoldan biriyle gerçekleşir:

  1. Satır sorgulandığında, işleminiz üzerinde paylaşılan bir kilit alır. Satırı güncelleştirmeye çalışan tüm dış işlemler, işleminiz tamamlanana kadar engellenir. Bu kötümser bir kilitleme biçimidir ve SQL Server "yinelenebilir okuma" yalıtım düzeyi tarafından uygulanır.
  2. Veritabanı kilitlemek yerine dış işlemin satırı güncelleştirmesine izin verir, ancak kendi işleminiz güncelleştirmeyi gerçekleştirmeye çalıştığında eşzamanlılık çakışması oluştuğunu belirten bir "serileştirme" hatası oluşur. Bu, EF'in eşzamanlılık belirteci özelliğinden farklı olmayan bir iyimser kilitleme biçimidir ve HEM SQL Server anlık görüntü yalıtım düzeyi hem de PostgreSQL yinelenebilir okuma yalıtım düzeyi tarafından uygulanır.

"Serileştirilebilir" yalıtım düzeyinin yinelenebilir okuma ile aynı garantileri sağladığını (ve eklerini eklediğini) unutmayın, bu nedenle yukarıdakilerle aynı şekilde çalışır.

Eşzamanlılık çakışmalarını yönetmek için daha yüksek bir yalıtım düzeyi kullanmak daha basittir, eşzamanlılık belirteçleri gerektirmez ve başka avantajlar sağlar; örneğin, yinelenebilir okuma işlemleri, tutarsızlıkları önleyerek işleminizin her zaman işlem içindeki sorgular arasında aynı verileri görmesini garanti eder. Ancak bu yaklaşımın dezavantajları vardır.

İlk olarak, veritabanı uygulamanız yalıtım düzeyini uygulamak için kilitlemeyi kullanıyorsa, aynı satırı değiştirmeye çalışan diğer işlemlerin işlemin tamamı için engellemesi gerekir. Bunun eşzamanlı performans üzerinde olumsuz bir etkisi olabilir (işleminizi kısa tutun!), ancak EF'in mekanizmasının bir özel durum oluşturduğunu ve bunun yerine sizi yeniden denemeye zorladığına ve bunun da bir etkisi olduğuna dikkat edin. Bu, SQL Server yinelenebilir okuma düzeyi için geçerlidir, ancak sorgulanan satırları kilitlemeyen anlık görüntü düzeyi için geçerli değildir.

Daha da önemlisi, bu yaklaşım tüm işlemleri kapsayan bir işlem gerektirir. Örneğin, bir kullanıcıya ayrıntılarını görüntülemek için sorgularsanız Person ve kullanıcının değişiklik yapmasını beklerseniz, işlem uzun süre boyunca hayatta kalmalıdır ve bu durum çoğu durumda önlenmelidir. Sonuç olarak, bu mekanizma genellikle tüm kapsanan işlemler hemen yürütülür ve işlem süresini artırabilecek dış girişlere bağımlı olmadığında uygundur.

Ek kaynaklar

Çakışma algılamalı bir ASP.NET Core örneği için bkz . EF Core'da çakışma algılama.