Неожиданный уровень изоляции транзакций в .NET с несколькими подключениями

Я собрал образец приложения, чтобы имитировать некоторые из основных функций кода нашего приложения. Пока я работал над этим, я немного ошибся в своем понимании TransactionScope и SqlConnection.

Проблема, которую я вижу, заключается в том, что я явно "вижу" IsolationLevel READ COMMITTED, где я ожидал SERIALIZABLE. Если только мои методы диагностики не ошибочны.

В настоящее время в моей базе данных включен Read Snapshot, поэтому на скриншотах отображается READ COMMITTED SNAPSHOT. Если этот параметр не включен, на снимках экрана будет отображаться READ COMMITTED.

Код

Код выполняется локально на моем компьютере, подключающемся к локальному экземпляру SQL Server 2014. Это минимальный необходимый код.

Итак, чтобы воспроизвести этот сценарий из консольного приложения:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Transactions;
using IsolationLevel = System.Transactions.IsolationLevel;

namespace IsolationLevelConsoleProblem
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var tran = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions
            {
                IsolationLevel = IsolationLevel.Serializable,
                Timeout = TimeSpan.FromMinutes(3)
            }))
            {
                using (var connectionForRead = new SqlConnection(GetConnectionString("For Read")))
                {
                    connectionForRead.Open();

                    using (var commandRead = new SqlCommand("usp_GetAllSimpleTable", connectionForRead))
                    {
                        commandRead.CommandType = CommandType.StoredProcedure;
                        commandRead.CommandTimeout = 120;

                        DataSet dataSet = new DataSet();
                        SqlDataAdapter adapter = new SqlDataAdapter
                        {
                            SelectCommand = commandRead
                        };

                        adapter.Fill(dataSet);
                    }
                }

                using (var connectionForUpdate = new SqlConnection(GetConnectionString("For Update")))
                {
                    connectionForUpdate.Open();

                    using (var commandUpdate = new SqlCommand("usp_UpdateSimpleTable", connectionForUpdate))
                    {
                        commandUpdate.CommandTimeout = 120;
                        commandUpdate.CommandType = CommandType.StoredProcedure;
                        commandUpdate.Parameters.AddWithValue("@Guid", Guid.Parse("{74B3155E-138E-4881-BEE2-67FD141E29DA}"));
                        commandUpdate.Parameters.AddWithValue("@Name", $"Name {DateTime.UtcNow}");
                        commandUpdate.ExecuteNonQuery();
                    }
                }

                tran.Complete();
            }
        }

        private static string GetConnectionString(string name)
        {
            SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
            builder.DataSource = ".\\SQL2014";
            builder.InitialCatalog = "AcmeTransactions";
            builder.IntegratedSecurity = true;
            builder.MaxPoolSize = 200;
            builder.ConnectTimeout = 120;
            builder.ApplicationName = name;

            return builder.ToString();
        }
    }
}

База данных

ПРИМЕЧАНИЕ. Я вручную добавил строку в таблицу для идентификатора GUID, указанного выше в коде. Я также добавил задержку ожидания в хранимую процедуру обновления, чтобы просмотреть уровень изоляции в сеансах.

Создать таблицу

CREATE TABLE [dbo].[SimpleTable](
    [Guid] [uniqueidentifier] NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_SimpleTable] PRIMARY KEY CLUSTERED 
(
    [Guid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

Сохраненная процедура для чтения

CREATE PROCEDURE [dbo].[usp_GetAllSimpleTable]
AS
BEGIN
    SELECT [Guid]
          ,[Name]
      FROM [dbo].[SimpleTable]

END

Сохраненная процедура для обновления

create PROCEDURE [dbo].[usp_UpdateSimpleTable]
(
    @Guid UNIQUEIDENTIFIER,
    @Name NVARCHAR(50)
)
AS
BEGIN
    UPDATE [dbo].[SimpleTable]
       SET [Name] = @Name
     WHERE [Guid] = @Guid

     waitfor delay '00:00:40'
END

Диагностика

Я использую SQL для перечисления сеансов, включая уровень изоляции и выполняемый SQL. Я верю в правильность этого утверждения. Я также попытался вернуть явную проверку уровня изоляции в самой хранимой процедуре, которая возвращает READ COMMITTED.

SELECT 
          d.name
        , s.session_id
        , CASE  
              WHEN s.transaction_isolation_level = 1 
                 THEN 'READ UNCOMMITTED' 
              WHEN s.transaction_isolation_level = 2 
                   AND is_read_committed_snapshot_on = 1 
                 THEN 'READ COMMITTED SNAPSHOT' 
              WHEN s.transaction_isolation_level = 2 
                   AND is_read_committed_snapshot_on = 0 THEN 'READ COMMITTED' 
              WHEN s.transaction_isolation_level = 3 
                 THEN 'REPEATABLE READ' 
              WHEN s.transaction_isolation_level = 4 
                 THEN 'SERIALIZABLE' 
              WHEN s.transaction_isolation_level = 5 
                 THEN 'SNAPSHOT' 
              ELSE NULL
           END
        , sqltext.TEXT

FROM   sys.dm_exec_sessions AS s
CROSS JOIN sys.databases AS d

join sys.dm_exec_requests er on s.session_id = er.session_id
CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) AS sqltext

where d.name ='AcmeTransactions' 
and s.program_name not in ('Microsoft SQL Server Management Studio - Query' , 'Microsoft SQL Server Management Studio')

Поэтому, когда вышеуказанное выполняется во время задержки, я вижу следующее:

Выполнение запроса

Дальнейшее расследование

Если блок кода connectionForRead с использованием перемещается за пределы транзакции, я вижу следующую информацию о изоляции сеанса из SSMS:

Ожидаемый уровень изоляции

Вот как я бы воспринимал немодифицированный код.

Вопросов

  1. Мой диагноз правильный? Оба соединения SQL работают как READ COMMITTED?
  2. Это лучший способ определить уровень транзакции выполняемых запросов?

person Andez    schedule 06.12.2017    source источник


Ответы (1)


Вы увидите такое поведение, если используете раннюю версию SQL Server 2014. Самое простое решение - применить SP2 (или выше).

См. https://support.microsoft.com/en-us/help/3025845/fix-the-transaction-isolation-level-is-reset-incorrectly-when-the-sql для получения дополнительных сведений.

person David Betteridge    schedule 08.12.2017
comment
Действительно ... Версия SQL - Microsoft SQL Server 2014 - 12.0.4100.1 (X64) Apr 20 2015 17:29:27 Copyright (c) Microsoft Corporation Standard Edition (64-bit) on Windows NT 6.3 <X64> (Build 15063: ) (Hypervisor) . Собираюсь в сервис пак. - person Andez; 08.12.2017
comment
Просто применил SP2 и проверил, что проблема решена, спасибо - person Andez; 08.12.2017