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

时间:2017-12-06 11:05:14

标签: c# sql-server transactionscope isolation-level transaction-isolation

我已经整理了一个示例应用程序来模仿我们的一些主要应用程序代码功能。在研究这个问题的同时,我对TransactionScopeSqlConnection的理解已经引起了一些曲解。

我看到的问题是明确地“看到”READ COMMITTED的IsolationLevel,我期望SERIALIZABLE。除非我的方法诊断有缺陷。

目前我的数据库已启用Read Snapshot,因此屏幕截图显示READ COMMITTED SNAPSHOT。如果未启用此功能,则屏幕截图将显示READ COMMITTED

代码

代码在我的机器上本地运行,连接到SQL Server 2014的本地实例。这是所需的最小代码。

所以要从Console应用程序复制这个场景:

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')

因此,当延迟进行时执行上述操作时,我会看到以下内容:

Running the query

进一步调查

如果使用代码块的connectionForRead移动到Transaction之外,那么我会从SSMS看到以下会话隔离信息:

Expected Isolation Level

这就是我将如何看待未经修改的代码。

问题

  1. 我的诊断是否正确?这两个SQL连接是以READ COMMITTED运行的吗?
  2. 这是确定运行查询的事务级别的最佳方法吗?

1 个答案:

答案 0 :(得分:1)

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

有关详细信息,请参阅https://support.microsoft.com/en-us/help/3025845/fix-the-transaction-isolation-level-is-reset-incorrectly-when-the-sql

相关问题