SQL Server'da Anlık Görüntü Yalıtımı

Anlık görüntü yalıtımı, OLTP uygulamaları için eşzamanlılığı artırır.

Anlık Görüntü Yalıtımını ve Satır Sürümünü Anlama

Anlık görüntü yalıtımı etkinleştirildikten sonra, her işlem için güncelleştirilmiş satır sürümleri korunmalıdır. SQL Server 2019'den önce bu sürümler tempdb'de depolanıyordu. SQL Server 2019, kendi satır sürümleri kümesini gerektiren Hızlandırılmış Veritabanı Kurtarma (ADR) adlı yeni bir özellik tanıtır. Bu nedenle, SQL Server 2019 itibarıyla ADR etkinleştirilmemişse satır sürümleri her zaman olduğu gibi tempdb'de tutulur. ADR etkinleştirilirse, hem anlık görüntü yalıtımı hem de ADR ile ilgili tüm satır sürümleri, kullanıcının belirttiği bir dosya grubundaki kullanıcı veritabanında bulunan ADR'nin Kalıcı Sürüm Deposu'nda (PVS) tutulur. Benzersiz bir işlem sırası numarası her işlemi tanımlar ve bu benzersiz sayılar her satır sürümü için kaydedilir. İşlem, işlemin sıra numarasından önce sıra numarasına sahip en son satır sürümleriyle çalışır. İşlem başlatıldıktan sonra oluşturulan daha yeni satır sürümleri işlem tarafından yoksayılır.

"Anlık görüntü" terimi, işlemdeki tüm sorguların, işlemin başladığı anda veritabanının durumuna bağlı olarak veritabanının aynı sürümünü veya anlık görüntüsünü gördüğü gerçeğini yansıtır. Anlık görüntü işleminde temel alınan veri satırlarına veya veri sayfalarına kilit alınmaz ve bu da diğer işlemlerin daha önce tamamlanmamış bir işlem tarafından engellenmeden yürütülmesine izin verir. Verileri değiştiren işlemler, verileri okuyan işlemleri engellemez ve verileri okuyan işlemler, normalde SQL Server'da varsayılan READ COMMITTED yalıtım düzeyi altında olduğu gibi veri yazan işlemleri engellemez. Bu engelleyici olmayan davranış, karmaşık işlemler için kilitlenme olasılığını da önemli ölçüde azaltır.

Anlık görüntü yalıtımı iyimser bir eşzamanlılık modeli kullanır. Bir anlık görüntü işlemi, işlem başladıktan sonra değişen verilerde değişiklikler gerçekleştirmeye çalışırsa, işlem geri alınır ve bir hata oluşur. Değiştirilecek verilere erişen SELECT deyimleri için UPDLOCK ipuçlarını kullanarak bunu önleyebilirsiniz. Daha fazla bilgi için bkz . İpuçları (Transact-SQL).

Anlık görüntü yalıtımı, işlemlerde kullanılmadan önce ALLOW_SNAPSHOT_ISOLATION ON veritabanı seçeneği ayarlanarak etkinleştirilmelidir. Bu, satır sürümlerini geçici veritabanında (tempdb) depolama mekanizmasını etkinleştirir. Transact-SQL ALTER DATABASE deyimiyle kullanan her veritabanında anlık görüntü yalıtımını etkinleştirmeniz gerekir. Bu açıdan anlık görüntü yalıtımı, yapılandırma gerektirmeyen READ COMMITTED, REPEATABLE READ, SERIALIZABLE ve READ UNCOMMITTED gibi geleneksel yalıtım düzeylerinden farklıdır. Aşağıdaki deyimler anlık görüntü yalıtımını etkinleştirir ve varsayılan READ COMMITTED davranışını SNAPSHOT ile değiştirir:

ALTER DATABASE MyDatabase  
SET ALLOW_SNAPSHOT_ISOLATION ON  
  
ALTER DATABASE MyDatabase  
SET READ_COMMITTED_SNAPSHOT ON  

READ_COMMITTED_SNAPSHOT ON seçeneğinin ayarlanması, varsayılan READ COMMITTED yalıtım düzeyi altındaki sürümlenmiş satırlara erişim sağlar. READ_COMMITTED_SNAPSHOT seçeneği KAPALI olarak ayarlandıysa, sürüme alınan satırlara erişmek için her oturum için Anlık görüntü yalıtım düzeyini açıkça ayarlamanız gerekir.

Yalıtım Düzeyleriyle Eşzamanlılığı Yönetme

Transact-SQL deyiminin yürütüleceği yalıtım düzeyi, kilitleme ve satır sürüm oluşturma davranışını belirler. Yalıtım düzeyi, bağlantı genelinde kapsama sahiptir ve SET TRANSACTION ISOLATION LEVEL deyimiyle bir bağlantı için ayarlandıktan sonra, bağlantı kapatılana veya başka bir yalıtım düzeyi ayarlanana kadar etkin kalır. Bir bağlantı kapatılıp havuza döndürildiğinde, son SET TRANSACTION ISOLATION LEVEL deyiminden yalıtım düzeyi korunur. Havuza alınan bağlantıyı yeniden kullanan sonraki bağlantılar, bağlantı havuza alındığı sırada geçerli olan yalıtım düzeyini kullanır.

Bir bağlantı içinde verilen tek tek sorgular, tek bir deyim veya işlem için yalıtımı değiştiren ancak bağlantının yalıtım düzeyini etkilemeyen kilit ipuçları içerebilir. Saklı yordamlarda veya işlevlerde ayarlanan yalıtım düzeyleri veya kilit ipuçları, onları çağıran bağlantının yalıtım düzeyini değiştirmez ve yalnızca saklı yordam veya işlev çağrısı süresi boyunca etkindir.

SQL-92 standardında tanımlanan dört yalıtım düzeyi, SQL Server'ın ilk sürümlerinde destekleniyordu:

  • READ UNCOMMITTED, diğer işlemler tarafından yerleştirilen kilitleri yoksaydığından en az kısıtlayıcı yalıtım düzeyidir. READ UNCOMMITTED altında yürütülen işlemler, diğer işlemler tarafından henüz işlenmemiş değiştirilmiş veri değerlerini okuyabilir; bunlara "kirli" okuma denir.

  • READ COMMITTED, SQL Server için varsayılan yalıtım düzeyidir. Deyimlerin değiştirilmiş ancak henüz diğer işlemler tarafından işlenmemiş veri değerlerini okuyamayacağını belirterek kirli okumaları önler. Diğer işlemler yine de geçerli işlem içindeki tek tek deyimlerin yürütmeleri arasında verileri değiştirebilir, ekleyebilir veya silebilir ve bu da yinelenemeyen okumalar veya "hayalet" verilerle sonuçlanabilir.

  • REPEATABLE READ, READ COMMITTED değerinden daha kısıtlayıcı bir yalıtım düzeyidir. READ COMMITTED'ı kapsar ve ayrıca geçerli işlem işlemeye kadar geçerli işlem tarafından okunan verileri başka hiçbir işlemin değiştiremeden veya silemez olduğunu belirtir. Okunan verilerdeki paylaşılan kilitler her deyimin sonunda yayımlanmak yerine işlem süresi boyunca tutulacağından eşzamanlılık READ COMMITTED değerinden daha düşüktür.

  • SERIALIZABLE en kısıtlayıcı yalıtım düzeyidir çünkü tüm anahtar aralıklarını kilitler ve işlem tamamlanana kadar kilitleri tutar. REPEATABLE READ'i kapsar ve işlem tamamlanana kadar diğer işlemlerin işlem tarafından okunan aralıklara yeni satır ekleyememesi kısıtlamasını ekler.

Daha fazla bilgi için İşlem Kilitleme ve Satır Sürüm Oluşturma Kılavuzu'na bakın.

Anlık Görüntü Yalıtım Düzeyi Uzantıları

SQL Server, SNAPSHOT yalıtım düzeyinin kullanıma sunulması ve READ COMMITTED uygulamasının ek uygulanmasıyla SQL-92 yalıtım düzeylerine uzantılar getirmektedir. READ_COMMITTED_SNAPSHOT yalıtım düzeyi, tüm işlemler için READ COMMITTED değerini saydam bir şekilde değiştirebilir.

  • SNAPSHOT yalıtımı, bir işlem içinde okunan verilerin hiçbir zaman diğer eşzamanlı işlemler tarafından yapılan değişiklikleri yansıtmayacağını belirtir. İşlem, işlem başladığında var olan veri satırı sürümlerini kullanır. Okunduğunda verilere kilit uygulanmaz, bu nedenle ANLıK GÖRÜNTÜ işlemleri diğer işlemlerin veri yazmasını engellemez. Veri yazan işlemler, anlık görüntü işlemlerinin verileri okumalarını engellemez. Kullanmak için ALLOW_SNAPSHOT_ISOLATION veritabanı seçeneğini ayarlayarak anlık görüntü yalıtımını etkinleştirmeniz gerekir.

  • READ_COMMITTED_SNAPSHOT veritabanı seçeneği, veritabanında anlık görüntü yalıtımı etkinleştirildiğinde varsayılan READ COMMITTED yalıtım düzeyinin davranışını belirler. açık READ_COMMITTED_SNAPSHOT belirtmezseniz, READ COMMITTED tüm örtük işlemlere uygulanır. Bu, READ_COMMITTED_SNAPSHOT OFF (varsayılan) ayarıyla aynı davranışı üretir. READ_COMMITTED_SNAPSHOT KAPALI olduğunda, Veritabanı Altyapısı varsayılan yalıtım düzeyini zorlamak için paylaşılan kilitleri kullanır. READ_COMMITTED_SNAPSHOT veritabanı seçeneğini ON olarak ayarlarsanız, veritabanı altyapısı verileri korumak için kilitleri kullanmak yerine varsayılan olarak satır sürümü oluşturma ve anlık görüntü yalıtımı kullanır.

Anlık Görüntü Yalıtımı ve Satır Sürümü Oluşturma Nasıl Çalışır?

SNAPSHOT yalıtım düzeyi etkinleştirildiğinde, bir satır her güncelleştirildiğinde, SQL Server Veritabanı Altyapısı özgün satırın bir kopyasını tempdb'de depolar ve satıra bir işlem dizisi numarası ekler. Gerçekleşen olayların sırası aşağıdadır:

  • Yeni bir işlem başlatılır ve bir işlem sırası numarası atanır.

  • Veritabanı Altyapısı, işlem içindeki bir satırı okur ve sıra numarası işlem dizisi numarasına en yakın ve daha düşük olan tempdb'den satır sürümünü alır.

  • Veritabanı Altyapısı, anlık görüntü işlemi başlatıldığında işlem sırası numarasının, kaydedilmemiş işlemlerin etkin işlem dizisi numaraları listesinde olup olmadığını denetler.

  • İşlem, hareketin başlangıcından itibaren geçerli olan tempdb'den satırın sürümünü okur. İşlem başlatıldıktan sonra eklenen yeni satırları görmez çünkü bu sıra numarası değerleri işlem dizisi numarasının değerinden daha yüksek olacaktır.

  • Geçerli işlem, işlem başladıktan sonra silinen satırları görür çünkü tempdb'de daha düşük bir sıra numarası değerine sahip bir satır sürümü olacaktır.

Anlık görüntü yalıtımının net etkisi, işlemin temel alınan tablolara herhangi bir kilit eklemeden, işlemin başlangıcında mevcut olan tüm verileri görmesidir. Bu, çekişme olduğu durumlarda performans geliştirmelerine neden olabilir.

Anlık görüntü işlemi her zaman iyimser eşzamanlılık denetimi kullanır ve diğer işlemlerin satırları güncelleştirmesini engelleyecek kilitleri engeller. Anlık görüntü işlemi, işlem başladıktan sonra değiştirilen bir satıra güncelleştirme işlemeye çalışırsa, işlem geri alınır ve bir hata oluşur.

ADO.NET'da Anlık Görüntü Yalıtımı ile Çalışma

Anlık görüntü yalıtımı sınıfı tarafından SqlTransaction ADO.NET desteklenir. Bir veritabanı anlık görüntü yalıtımı için etkinleştirildiyse ancak READ_COMMITTED_SNAPSHOT ON için yapılandırılmadıysa, yöntemini çağırırken BeginTransaction IsolationLevel.Snapshot numaralandırma değerini kullanarak bir SqlTransaction başlatmanız gerekir. Bu kod parçası, bağlantının açık SqlConnection bir nesne olduğunu varsayar.

Dim sqlTran As SqlTransaction = _  
  connection.BeginTransaction(IsolationLevel.Snapshot)  
SqlTransaction sqlTran =
  connection.BeginTransaction(IsolationLevel.Snapshot);  

Örnek

Aşağıdaki örnek, kilitli verilere erişmeye çalışarak farklı yalıtım düzeylerinin nasıl davrandığını gösterir ve üretim kodunda kullanılması amaçlanmamıştır.

Kod, SQL Server'daki AdventureWorks örnek veritabanına bağlanır ve TestSnapshot adlı bir tablo oluşturur ve bir veri satırı ekler. Kod, veritabanı için anlık görüntü yalıtımını açmak için ALTER DATABASE Transact-SQL deyimini kullanır, ancak varsayılan READ COMMITTED yalıtım düzeyi davranışını etkin bırakarak READ_COMMITTED_SNAPSHOT seçeneğini ayarlamaz. Kod daha sonra aşağıdaki eylemleri gerçekleştirir:

  • Güncelleştirme işlemini başlatmak için SERIALIZABLE yalıtım düzeyini kullanan sqlTransaction1 başlar ancak tamamlanmaz. Bu, tabloyu kilitleme etkisine sahiptir.

  • İkinci bir bağlantı açar ve TestSnapshot tablosundaki verileri okumak için SNAPSHOT yalıtım düzeyini kullanarak ikinci bir işlem başlatır. Anlık görüntü yalıtımı etkinleştirildiğinden, bu işlem sqlTransaction1 başlatılmadan önce var olan verileri okuyabilir.

  • Üçüncü bir bağlantı açar ve tablodaki verileri okumaya çalışmak için READ COMMITTED yalıtım düzeyini kullanarak bir işlem başlatır. Bu durumda kod, ilk işlemde tabloya yerleştirilen kilitleri okuyamadığından verileri okuyamaz ve zaman aşımına uğradı. Bu yalıtım düzeyleri ilk işleme yerleştirilen kilitleri de okuyamadığından REPEATABLE READ ve SERIALIZABLE yalıtım düzeyleri kullanıldıysa aynı sonuç ortaya çıkar.

  • Dördüncü bir bağlantı açar ve SQLTransaction1'de kaydedilmemiş değerin kirli bir okumasını gerçekleştiren READ UNCOMMITTED yalıtım düzeyini kullanarak bir işlem başlatır. İlk işlem işlenmediyse bu değer veritabanında hiçbir zaman mevcut olmayabilir.

  • İlk işlemi geri alır ve TestSnapshot tablosunu silerek ve AdventureWorks veritabanı için anlık görüntü yalıtımını kapatarak temizler.

Not

Aşağıdaki örneklerde, bağlantı havuzu kapalıyken aynı bağlantı dizesi kullanılır. Bir bağlantı havuza alındıysa, yalıtım düzeyi sıfırlandığında sunucudaki yalıtım düzeyi sıfırlanmaz. Sonuç olarak, aynı havuza alınan iç bağlantıyı kullanan sonraki bağlantılar, yalıtım düzeyleri havuza alınan bağlantınınkine ayarlanmış şekilde başlar. Bağlantı havuzunu kapatmanın bir alternatifi, her bağlantı için yalıtım düzeyini açıkça ayarlamaktır.

// Assumes GetConnectionString returns a valid connection string
// where pooling is turned off by setting Pooling=False;.
var connectionString = GetConnectionString();
using (SqlConnection connection1 = new(connectionString))
{
    // Drop the TestSnapshot table if it exists
    connection1.Open();
    SqlCommand command1 = connection1.CreateCommand();
    command1.CommandText = "IF EXISTS "
        + "(SELECT * FROM sys.tables WHERE name=N'TestSnapshot') "
        + "DROP TABLE TestSnapshot";
    try
    {
        command1.ExecuteNonQuery();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    // Enable Snapshot isolation
    command1.CommandText =
        "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION ON";
    command1.ExecuteNonQuery();

    // Create a table named TestSnapshot and insert one row of data
    command1.CommandText =
        "CREATE TABLE TestSnapshot (ID int primary key, valueCol int)";
    command1.ExecuteNonQuery();
    command1.CommandText =
        "INSERT INTO TestSnapshot VALUES (1,1)";
    command1.ExecuteNonQuery();

    // Begin, but do not complete, a transaction to update the data
    // with the Serializable isolation level, which locks the table
    // pending the commit or rollback of the update. The original
    // value in valueCol was 1, the proposed new value is 22.
    SqlTransaction transaction1 =
        connection1.BeginTransaction(IsolationLevel.Serializable);
    command1.Transaction = transaction1;
    command1.CommandText =
        "UPDATE TestSnapshot SET valueCol=22 WHERE ID=1";
    command1.ExecuteNonQuery();

    // Open a second connection to AdventureWorks
    using (SqlConnection connection2 = new(connectionString))
    {
        connection2.Open();
        // Initiate a second transaction to read from TestSnapshot
        // using Snapshot isolation. This will read the original
        // value of 1 since transaction1 has not yet committed.
        SqlCommand command2 = connection2.CreateCommand();
        SqlTransaction transaction2 =
            connection2.BeginTransaction(IsolationLevel.Snapshot);
        command2.Transaction = transaction2;
        command2.CommandText =
            "SELECT ID, valueCol FROM TestSnapshot";
        SqlDataReader reader2 = command2.ExecuteReader();
        while (reader2.Read())
        {
            Console.WriteLine("Expected 1,1 Actual "
                + reader2.GetValue(0)
                + "," + reader2.GetValue(1));
        }
        transaction2.Commit();
    }

    // Open a third connection to AdventureWorks and
    // initiate a third transaction to read from TestSnapshot
    // using ReadCommitted isolation level. This transaction
    // will not be able to view the data because of
    // the locks placed on the table in transaction1
    // and will time out after 4 seconds.
    // You would see the same behavior with the
    // RepeatableRead or Serializable isolation levels.
    using (SqlConnection connection3 = new(connectionString))
    {
        connection3.Open();
        SqlCommand command3 = connection3.CreateCommand();
        SqlTransaction transaction3 =
            connection3.BeginTransaction(IsolationLevel.ReadCommitted);
        command3.Transaction = transaction3;
        command3.CommandText =
            "SELECT ID, valueCol FROM TestSnapshot";
        command3.CommandTimeout = 4;
        try
        {
            SqlDataReader sqldatareader3 = command3.ExecuteReader();
            while (sqldatareader3.Read())
            {
                Console.WriteLine("You should never hit this.");
            }
            transaction3.Commit();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Expected timeout expired exception: "
                + ex.Message);
            transaction3.Rollback();
        }
    }

    // Open a fourth connection to AdventureWorks and
    // initiate a fourth transaction to read from TestSnapshot
    // using the ReadUncommitted isolation level. ReadUncommitted
    // will not hit the table lock, and will allow a dirty read
    // of the proposed new value 22 for valueCol. If the first
    // transaction rolls back, this value will never actually have
    // existed in the database.
    using (SqlConnection connection4 = new(connectionString))
    {
        connection4.Open();
        SqlCommand command4 = connection4.CreateCommand();
        SqlTransaction transaction4 =
            connection4.BeginTransaction(IsolationLevel.ReadUncommitted);
        command4.Transaction = transaction4;
        command4.CommandText =
            "SELECT ID, valueCol FROM TestSnapshot";
        SqlDataReader reader4 = command4.ExecuteReader();
        while (reader4.Read())
        {
            Console.WriteLine("Expected 1,22 Actual "
                + reader4.GetValue(0)
                + "," + reader4.GetValue(1));
        }

        transaction4.Commit();
    }

    // Roll back the first transaction
    transaction1.Rollback();
}

// CLEANUP
// Delete the TestSnapshot table and set
// ALLOW_SNAPSHOT_ISOLATION OFF
using (SqlConnection connection5 = new(connectionString))
{
    connection5.Open();
    SqlCommand command5 = connection5.CreateCommand();
    command5.CommandText = "DROP TABLE TestSnapshot";
    SqlCommand command6 = connection5.CreateCommand();
    command6.CommandText =
        "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION OFF";
    try
    {
        command5.ExecuteNonQuery();
        command6.ExecuteNonQuery();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}
Console.WriteLine("Done!");
' Assumes GetConnectionString returns a valid connection string
' where pooling is turned off by setting Pooling=False;. 
Dim connectionString As String = GetConnectionString()

Using connection1 As New SqlConnection(connectionString)
    ' Drop the TestSnapshot table if it exists
    connection1.Open()
    Dim command1 As SqlCommand = connection1.CreateCommand
    command1.CommandText = "IF EXISTS " & _
    "(SELECT * FROM sys.tables WHERE name=N'TestSnapshot') " _
      & "DROP TABLE TestSnapshot"
    Try
        command1.ExecuteNonQuery()
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    End Try

    ' Enable SNAPSHOT isolation 
    command1.CommandText = _
    "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION ON"
    command1.ExecuteNonQuery()

    ' Create a table named TestSnapshot and insert one row of data
    command1.CommandText = _
    "CREATE TABLE TestSnapshot (ID int primary key, valueCol int)"
    command1.ExecuteNonQuery()
    command1.CommandText = _
      "INSERT INTO TestSnapshot VALUES (1,1)"
    command1.ExecuteNonQuery()

    ' Begin, but do not complete, a transaction to update the data 
    ' with the Serializable isolation level, which locks the table
    ' pending the commit or rollback of the update. The original 
    ' value in valueCol was 1, the proposed new value is 22.
    Dim transaction1 As SqlTransaction = _
      connection1.BeginTransaction(IsolationLevel.Serializable)
    command1.Transaction = transaction1
    command1.CommandText = _
     "UPDATE TestSnapshot SET valueCol=22 WHERE ID=1"
    command1.ExecuteNonQuery()

    ' Open a second connection to AdventureWorks
    Dim connection2 As SqlConnection = New SqlConnection(connectionString)
    Using connection2
        connection2.Open()

        ' Initiate a second transaction to read from TestSnapshot
        ' using Snapshot isolation. This will read the original 
        ' value of 1 since transaction1 has not yet committed.
        Dim command2 As SqlCommand = connection2.CreateCommand()
        Dim transaction2 As SqlTransaction = _
          connection2.BeginTransaction(IsolationLevel.Snapshot)
        command2.Transaction = transaction2
        command2.CommandText = _
            "SELECT ID, valueCol FROM TestSnapshot"
        Dim reader2 As SqlDataReader = _
            command2.ExecuteReader()
        While reader2.Read()
            Console.WriteLine("Expected 1,1 Actual " _
              & reader2.GetValue(0).ToString() + "," _
              & reader2.GetValue(1).ToString())
        End While
        transaction2.Commit()
    End Using

    ' Open a third connection to AdventureWorks and
    ' initiate a third transaction to read from TestSnapshot
    ' using the ReadCommitted isolation level. This transaction
    ' will not be able to view the data because of 
    ' the locks placed on the table in transaction1
    ' and will time out after 4 seconds.
    ' You would see the same behavior with the
    ' RepeatableRead or Serializable isolation levels.
    Dim connection3 As SqlConnection = New SqlConnection(connectionString)
    Using connection3
        connection3.Open()
        Dim command3 As SqlCommand = connection3.CreateCommand()
        Dim transaction3 As SqlTransaction = _
            connection3.BeginTransaction(IsolationLevel.ReadCommitted)
        command3.Transaction = transaction3
        command3.CommandText = _
            "SELECT ID, valueCol FROM TestSnapshot"
        command3.CommandTimeout = 4

        Try
            Dim reader3 As SqlDataReader = command3.ExecuteReader()
            While reader3.Read()
                Console.WriteLine("You should never hit this.")
            End While
            transaction3.Commit()
        Catch ex As Exception
            Console.WriteLine("Expected timeout expired exception: " _
              & ex.Message)
            transaction3.Rollback()
        End Try
    End Using

    ' Open a fourth connection to AdventureWorks and
    ' initiate a fourth transaction to read from TestSnapshot
    ' using the ReadUncommitted isolation level. ReadUncommitted
    ' will not hit the table lock, and will allow a dirty read  
    ' of the proposed new value 22. If the first transaction 
    ' transaction rolls back, this value will never actually have
    ' existed in the database.
    Dim connection4 As SqlConnection = New SqlConnection(connectionString)
    Using connection4
        connection4.Open()
        Dim command4 As SqlCommand = connection4.CreateCommand()
        Dim transaction4 As SqlTransaction = _
          connection4.BeginTransaction(IsolationLevel.ReadUncommitted)
        command4.Transaction = transaction4
        command4.CommandText = _
            "SELECT ID, valueCol FROM TestSnapshot"
        Dim reader4 As SqlDataReader = _
            command4.ExecuteReader()
        While reader4.Read()
            Console.WriteLine("Expected 1,22 Actual " _
              & reader4.GetValue(0).ToString() _
              & "," + reader4.GetValue(1).ToString())
        End While
        transaction4.Commit()

        ' Rollback transaction1
        transaction1.Rollback()
    End Using
End Using

' CLEANUP
' Drop TestSnapshot table and set
' ALLOW_SNAPSHOT_ISOLATION OFF for AdventureWorks
Dim connection5 As New SqlConnection(connectionString)
Using connection5
    connection5.Open()
    Dim command5 As SqlCommand = connection5.CreateCommand()
    command5.CommandText = "DROP TABLE TestSnapshot"
    Dim command6 As SqlCommand = connection5.CreateCommand()
    command6.CommandText = _
   "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION OFF"
    Try
        command5.ExecuteNonQuery()
        command6.ExecuteNonQuery()
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    End Try
End Using

Örnek

Aşağıdaki örnek, veriler değiştirilirken anlık görüntü yalıtımının davranışını gösterir. Kod aşağıdaki eylemleri gerçekleştirir:

  • BağlanAdventureWorks örnek veritabanı ve SNAPSHOT yalıtımını etkinleştirir.

  • TestSnapshotUpdate adlı bir tablo oluşturur ve üç örnek veri satırı ekler.

  • ANLıK GÖRÜNTÜ yalıtımı kullanarak sqlTransaction1 başlar, ancak tamamlanmaz. İşlemde üç veri satırı seçilir.

  • AdventureWorks için ikinci bir Sql Bağlan ion oluşturur ve READ COMMITTED yalıtım düzeyini kullanarak sqlTransaction1 içinde seçilen satırlardan birinde bir değeri güncelleştiren ikinci bir işlem oluşturur.

  • commits sqlTransaction2.

  • sqlTransaction1'e döner ve sqlTransaction1'in zaten işlediği satırı güncelleştirmeye çalışır. Hata 3960 oluştu ve sqlTransaction1 otomatik olarak geri alınır. SqlException.Number ve SqlException.Message, Konsol penceresinde görüntülenir.

  • AdventureWorks'te anlık görüntü yalıtımını kapatmak ve TestSnapshotUpdate tablosunu silmek için temizleme kodunu yürütür.

// Assumes GetConnectionString returns a valid connection string
// where pooling is turned off by setting Pooling=False;.
var connectionString = GetConnectionString();
using (SqlConnection connection1 = new(connectionString))
{
    connection1.Open();
    SqlCommand command1 = connection1.CreateCommand();

    // Enable Snapshot isolation in AdventureWorks
    command1.CommandText =
        "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION ON";
    try
    {
        command1.ExecuteNonQuery();
        Console.WriteLine(
            "Snapshot Isolation turned on in AdventureWorks.");
    }
    catch (Exception ex)
    {
        Console.WriteLine("ALLOW_SNAPSHOT_ISOLATION ON failed: {0}", ex.Message);
    }
    // Create a table
    command1.CommandText =
        "IF EXISTS "
        + "(SELECT * FROM sys.tables "
        + "WHERE name=N'TestSnapshotUpdate')"
        + " DROP TABLE TestSnapshotUpdate";
    command1.ExecuteNonQuery();
    command1.CommandText =
        "CREATE TABLE TestSnapshotUpdate "
        + "(ID int primary key, CharCol nvarchar(100));";
    try
    {
        command1.ExecuteNonQuery();
        Console.WriteLine("TestSnapshotUpdate table created.");
    }
    catch (Exception ex)
    {
        Console.WriteLine("CREATE TABLE failed: {0}", ex.Message);
    }
    // Insert some data
    command1.CommandText =
        "INSERT INTO TestSnapshotUpdate VALUES (1,N'abcdefg');"
        + "INSERT INTO TestSnapshotUpdate VALUES (2,N'hijklmn');"
        + "INSERT INTO TestSnapshotUpdate VALUES (3,N'opqrstuv');";
    try
    {
        command1.ExecuteNonQuery();
        Console.WriteLine("Data inserted TestSnapshotUpdate table.");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }

    // Begin, but do not complete, a transaction
    // using the Snapshot isolation level.
    SqlTransaction transaction1 = default!;
    try
    {
        transaction1 = connection1.BeginTransaction(IsolationLevel.Snapshot);
        command1.CommandText =
            "SELECT * FROM TestSnapshotUpdate WHERE ID BETWEEN 1 AND 3";
        command1.Transaction = transaction1;
        command1.ExecuteNonQuery();
        Console.WriteLine("Snapshot transaction1 started.");

        // Open a second Connection/Transaction to update data
        // using ReadCommitted. This transaction should succeed.
        using (SqlConnection connection2 = new(connectionString))
        {
            connection2.Open();
            SqlCommand command2 = connection2.CreateCommand();
            command2.CommandText = "UPDATE TestSnapshotUpdate SET CharCol="
                + "N'New value from Connection2' WHERE ID=1";
            SqlTransaction transaction2 =
                connection2.BeginTransaction(IsolationLevel.ReadCommitted);
            command2.Transaction = transaction2;
            try
            {
                command2.ExecuteNonQuery();
                transaction2.Commit();
                Console.WriteLine(
                    "transaction2 has modified data and committed.");
            }
            catch (SqlException ex)
            {
                Console.WriteLine(ex.Message);
                transaction2.Rollback();
            }
            finally
            {
                transaction2.Dispose();
            }
        }

        // Now try to update a row in Connection1/Transaction1.
        // This transaction should fail because Transaction2
        // succeeded in modifying the data.
        command1.CommandText =
            "UPDATE TestSnapshotUpdate SET CharCol="
            + "N'New value from Connection1' WHERE ID=1";
        command1.Transaction = transaction1;
        command1.ExecuteNonQuery();
        transaction1.Commit();
        Console.WriteLine("You should never see this.");
    }
    catch (SqlException ex)
    {
        Console.WriteLine("Expected failure for transaction1:");
        Console.WriteLine("  {0}: {1}", ex.Number, ex.Message);
    }
    finally
    {
        transaction1.Dispose();
    }
}

// CLEANUP:
// Turn off Snapshot isolation and delete the table
using (SqlConnection connection3 = new(connectionString))
{
    connection3.Open();
    SqlCommand command3 = connection3.CreateCommand();
    command3.CommandText =
        "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION OFF";
    try
    {
        command3.ExecuteNonQuery();
        Console.WriteLine(
            "CLEANUP: Snapshot isolation turned off in AdventureWorks.");
    }
    catch (Exception ex)
    {
        Console.WriteLine("CLEANUP FAILED: {0}", ex.Message);
    }
    command3.CommandText = "DROP TABLE TestSnapshotUpdate";
    try
    {
        command3.ExecuteNonQuery();
        Console.WriteLine("CLEANUP: TestSnapshotUpdate table deleted.");
    }
    catch (Exception ex)
    {
        Console.WriteLine("CLEANUP FAILED: {0}", ex.Message);
    }
}
' Assumes GetConnectionString returns a valid connection string
' where pooling is turned off by setting Pooling=False;. 
Dim connectionString As String = GetConnectionString()

Using connection1 As New SqlConnection(connectionString)
    ' Enable Snapshot isolation in AdventureWorks
    connection1.Open()
    Dim command1 As SqlCommand = connection1.CreateCommand
    command1.CommandText = _
   "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION ON;"
    Try
        command1.ExecuteNonQuery()
        Console.WriteLine( _
         "Snapshot Isolation turned on in AdventureWorks.")
    Catch ex As Exception
        Console.WriteLine("ALLOW_SNAPSHOT_ISOLATION failed: {0}", ex.Message)
    End Try

    ' Create a table
    command1.CommandText = _
      "IF EXISTS (SELECT * FROM sys.databases " _
      & "WHERE name=N'TestSnapshotUpdate') " _
      & "DROP TABLE TestSnapshotUpdate"
    command1.ExecuteNonQuery()
    command1.CommandText = _
      "CREATE TABLE TestSnapshotUpdate (ID int primary key, " _
      & "CharCol nvarchar(100));"
    Try
        command1.ExecuteNonQuery()
        Console.WriteLine("TestSnapshotUpdate table created.")
    Catch ex As Exception
        Console.WriteLine("CREATE TABLE failed: {0}", ex.Message)
    End Try

    ' Insert some data
    command1.CommandText = _
      "INSERT INTO TestSnapshotUpdate VALUES (1,N'abcdefg');" _
      & "INSERT INTO TestSnapshotUpdate VALUES (2,N'hijklmn');" _
      & "INSERT INTO TestSnapshotUpdate VALUES (3,N'opqrstuv');"
    Try
        command1.ExecuteNonQuery()
        Console.WriteLine("Data inserted TestSnapshotUpdate table.")
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    End Try

    ' Begin, but do not complete, a transaction
    ' using the Snapshot isolation level
    Dim transaction1 As SqlTransaction = Nothing
    Try
        transaction1 = connection1.BeginTransaction(IsolationLevel.Snapshot)
        command1.CommandText = _
          "SELECT * FROM TestSnapshotUpdate WHERE ID " _
          & "BETWEEN 1 AND 3"
        command1.Transaction = transaction1
        command1.ExecuteNonQuery()
        Console.WriteLine("Snapshot transaction1 started.")

        ' Open a second Connection/Transaction to update data
        ' using ReadCommitted. This transaction should succeed.
        Dim connection2 As SqlConnection = New SqlConnection(connectionString)
        Using connection2
            connection2.Open()
            Dim command2 As SqlCommand = connection2.CreateCommand()
            command2.CommandText = "UPDATE TestSnapshotUpdate SET " _
              & "CharCol=N'New value from Connection2' WHERE ID=1"
            Dim transaction2 As SqlTransaction = _
              connection2.BeginTransaction(IsolationLevel.ReadCommitted)
            command2.Transaction = transaction2
            Try
                command2.ExecuteNonQuery()
                transaction2.Commit()
                Console.WriteLine( _
                  "transaction2 has modified data and committed.")
            Catch ex As SqlException
                Console.WriteLine(ex.Message)
                transaction2.Rollback()
            Finally
                transaction2.Dispose()
            End Try
        End Using

        ' Now try to update a row in Connection1/Transaction1.
        ' This transaction should fail because Transaction2
        ' succeeded in modifying the data.
        command1.CommandText = _
          "UPDATE TestSnapshotUpdate SET CharCol=" _
            & "N'New value from Connection1' WHERE ID=1"
        command1.Transaction = transaction1
        command1.ExecuteNonQuery()
        transaction1.Commit()
        Console.WriteLine("You should never see this.")

    Catch ex As SqlException
        Console.WriteLine("Expected failure for transaction1:")
        Console.WriteLine("  {0}: {1}", ex.Number, ex.Message)
    Finally
        transaction1.Dispose()
    End Try
End Using

' CLEANUP:
' Turn off Snapshot isolation and delete the table
Dim connection3 As New SqlConnection(connectionString)
Using connection3
    connection3.Open()
    Dim command3 As SqlCommand = connection3.CreateCommand()
    command3.CommandText = _
  "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION OFF"
    Try
        command3.ExecuteNonQuery()
        Console.WriteLine( _
         "Snapshot isolation turned off in AdventureWorks.")
    Catch ex As Exception
        Console.WriteLine("CLEANUP FAILED: {0}", ex.Message)
    End Try

    command3.CommandText = "DROP TABLE TestSnapshotUpdate"
    Try
        command3.ExecuteNonQuery()
        Console.WriteLine("TestSnapshotUpdate table deleted.")
    Catch ex As Exception
        Console.WriteLine("CLEANUP FAILED: {0}", ex.Message)
    End Try
End Using

Anlık Görüntü Yalıtımı ile Kilit İpuçlarını Kullanma

Önceki örnekte, ilk işlem verileri seçer ve ikinci bir işlem ilk işlem tamamlanmadan önce verileri güncelleştirir ve ilk işlem aynı satırı güncelleştirmeye çalıştığında güncelleştirme çakışmasına neden olur. İşlemin başında kilit ipuçları sağlayarak uzun süre çalışan anlık görüntü işlemlerinde güncelleştirme çakışmaları olasılığını azaltabilirsiniz. Aşağıdaki SELECT deyimi, seçili satırları kilitlemek için UPDLOCK ipucunu kullanır:

SELECT * FROM TestSnapshotUpdate WITH (UPDLOCK)
  WHERE PriKey BETWEEN 1 AND 3  

UPDLOCK kilit ipucunu kullanmak, ilk işlem tamamlanmadan önce satırları güncelleştirmeye çalışan satırları engeller. Bu, seçilen satırların işlemde daha sonra güncelleştirildiğinde çakışma olmamasını garanti eder. Daha fazla bilgi için bkz . İpuçları (Transact-SQL).

Uygulamanızın birçok çakışması varsa, anlık görüntü yalıtımı en iyi seçenek olmayabilir. İpuçları yalnızca gerçekten gerektiğinde kullanılmalıdır. Uygulamanız, işlemi için sürekli kilit ipuçlarına dayalı olacak şekilde tasarlanmamalıdır.

Ayrıca bkz.