具有多个连接的 .NET 中意外的事务隔离级别

Unexpected Transaction Isolation Level in .NET with multiple connections

我整理了一个示例应用程序来模拟我们的一些主要应用程序代码功能。在进行这项工作时,我对 TransactionScopeSqlConnection.

的理解有点曲折

我看到的问题明确是 "see" 一个 READ COMMITTED 的 IsolationLevel,而我期望 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();
        }
    }
}

数据库

注意:我已经在 table 中为上面代码中指定的 GUID 手动添加了一行。我还在更新存储过程中添加了等待延迟,以查看会话中的隔离级别。

创建Table

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 的会话。这个我相信是正确的。我还尝试在 returns 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. 这是确定 运行 查询的事务级别的最佳方法吗?

如果您 运行 是 SQL Server 2014 的早期版本,您将看到此行为。最简单的解决方法是应用 SP2(或更高版本)

详情见https://support.microsoft.com/en-us/help/3025845/fix-the-transaction-isolation-level-is-reset-incorrectly-when-the-sql