0

我目前正在追究我们遇到的死锁和SQL Server问题。发生了很多死锁。下面僵局图是什么,我专注于(从现场环境):SQL事务隔离级别可串行化与读取在开发与生产中承诺

<deadlock> 
    <victim-list> 
     <victimProcess id="process41fce08"/> 
     <victimProcess id="process40c9048"/> 
    </victim-list> 
    <process-list> 
     <process id="process41fce08" taskpriority="0" logused="0" waitresource="KEY: 5:72057595257749504 (03e6337489b1)" waittime="2133" ownerId="603" transactionname="SELECT" lasttranstarted="2015-09-30T09:06:29.133" XDES="0xe1d9dcb0" lockMode="RangeS-S" schedulerid="4" kpid="3608" status="suspended" spid="113" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2015-09-30T09:06:29.133" lastbatchcompleted="2015-09-30T09:06:29.133" clientapp=".Net SqlClient Data Provider" hostname="MYSERVER" hostpid="1692" loginname="MYUSER" isolationlevel="serializable (4)" xactid="603" currentdb="5" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056"> 
      <executionStack> 
       <frame procname="" line="25" stmtstart="1876" stmtend="5346" sqlhandle="0x030005007f7dca111202de0024a400000100000000000000"/> 
      </executionStack> 
      <inputbuf> Proc [Database Id = 5 Object Id = 298483071] THIS IS READ STORED PROCEDURE </inputbuf> 
     </process> 
     <process id="process40c9048" taskpriority="0" logused="0" waitresource="KEY: 5:72057595257749504 (03e6337489b1)" waittime="2137" ownerId="579" transactionname="SELECT" lasttranstarted="2015-09-30T09:06:29.130" XDES="0x8051fcb0" lockMode="RangeS-S" schedulerid="2" kpid="3908" status="suspended" spid="101" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2015-09-30T09:06:29.130" lastbatchcompleted="2015-09-30T09:06:29.130" clientapp=".Net SqlClient Data Provider" hostname="MYSERVER" hostpid="5328" loginname="MYUSER" isolationlevel="serializable (4)" xactid="579" currentdb="5" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056"> 
      <executionStack> 
       <frame procname="" line="25" stmtstart="1876" stmtend="5346" sqlhandle="0x030005007f7dca111202de0024a400000100000000000000"/> 
      </executionStack> 
      <inputbuf> Proc [Database Id = 5 Object Id = 298483071] THIS IS READ STORED PROCEDURE </inputbuf> 
     </process> 
     <process id="process11eca3708" taskpriority="0" logused="247980" waitresource="OBJECT: 5:1745427475:0 " waittime="1913" ownerId="213208999" transactionname="user_transaction" lasttranstarted="2015-09-30T09:06:28.640" XDES="0xc2f3ae90" lockMode="IX" schedulerid="3" kpid="272" status="suspended" spid="72" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-09-30T09:06:29.410" lastbatchcompleted="2015-09-30T09:06:29.410" clientapp=".Net SqlClient Data Provider" hostname="MYSERVER" hostpid="4172" loginname="MYUSER" isolationlevel="serializable (4)" xactid="213208999" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> 
      <executionStack> 
       <frame procname="" line="6" stmtstart="232" sqlhandle="0x0300050098df89558d028500b59f00000100000000000000"/> 
      </executionStack> 
      <inputbuf> Proc [Database Id = 5 Object Id = 1435099032] THIS IS READ UPDATE PROCEDURE </inputbuf> 
     </process> 
    </process-list> 
    <resource-list> 
     <keylock hobtid="72057595257749504" dbid="5" objectname="" indexname="" id="lockee29f480" mode="X" associatedObjectId="72057595257749504"> 
      <owner-list/> 
      <waiter-list> 
       <waiter id="process41fce08" mode="RangeS-S" requestType="wait"/> 
      </waiter-list> 
     </keylock> 
     <keylock hobtid="72057595257749504" dbid="5" objectname="" indexname="" id="lockee29f480" mode="X" associatedObjectId="72057595257749504"> 
      <owner-list> 
       <owner id="process11eca3708" mode="X"/> 
      </owner-list> 
      <waiter-list> 
       <waiter id="process40c9048" mode="RangeS-S" requestType="wait"/> 
      </waiter-list> 
     </keylock> 
     <objectlock lockPartition="0" objid="1745427475" subresource="FULL" dbid="5" objectname="" id="lock128d27d80" mode="S" associatedObjectId="1745427475"> 
      <owner-list> 
       <owner id="process41fce08" mode="S"/> 
      </owner-list> 
      <waiter-list> 
       <waiter id="process11eca3708" mode="IX" requestType="convert"/> 
      </waiter-list> 
     </objectlock> 
    </resource-list> 
</deadlock> 

在死锁牺牲品进程id = process41fce08,这是一个选择的存储过程,会出现一个序列化的事务下运行展望。

但是,看着堆栈跟踪,不使用事务范围。没有创建明确的交易。一个简单的SqlConnection和SqlCommand用于执行存储过程。

查看调用堆栈以在本地开发机器上执行相同的代码路径,这显示了在ReadCommitted下运行的事务隔离级别。此信息使用以下内容检索:

select 
CASE transaction_isolation_level 
WHEN 0 THEN 'Unspecified' 
WHEN 1 THEN 'ReadUncommitted' 
WHEN 2 THEN 'ReadCommitted' 
WHEN 3 THEN 'Repeatable' 
WHEN 4 THEN 'Serializable' 
WHEN 5 THEN 'Snapshot' 
ELSE 'Unknown' 
END 
FROM sys.dm_exec_sessions 
where session_id = @@SPID 

从存储过程中。如果我拨打:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

在存储过程中,我看到隔离级别在上面的select中改变了。这就是我期待和我在开发环境中看到:

Default Isolation Level in ADO.NET

所以考虑到:

  1. 的TransactionScope不会被调用堆栈
  2. 设置任何隔离级别设置在存储过程中

东西似乎是在现场环境设置隔离级别?问题是什么是设置或迫使它是可序列化的?

的代码运行在一个MVC3应用在.NET 4

回答

0

这似乎是下降到连接池。

我在连接字符串中注意到,我们有以下几点:

Pooling=true; Min Pool Size=5; Max Pool Size=100; Connect Timeout=7 

我创建了一些单元测试来证明这一点。

SQL创建脚本

SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE PROCEDURE [dbo].[usp_GetName] 
(
    @id int, 
    @TransactionIsolation varchar(30) output 
) 
AS 
BEGIN 
    -- SET NOCOUNT ON added to prevent extra result sets from 
    -- interfering with SELECT statements. 
    SET NOCOUNT ON; 

    set @TransactionIsolation = dbo.fn_GetTransactionIsolation(); 

SELECT [Id] 
     ,[Name] 
     ,[Status] 
    FROM [dbo].[NameTable] 
    where Id = @id 

END 


CREATE FUNCTION [dbo].[fn_GetTransactionIsolation] 
(
) 
RETURNS varchar(30) 
AS 
BEGIN 
    -- Declare the return variable here 
    DECLARE @til varchar(30) 

    select @til = 
    CASE transaction_isolation_level 
    WHEN 0 THEN 'Unspecified' 
    WHEN 1 THEN 'ReadUncommitted' 
    WHEN 2 THEN 'ReadCommitted' 
    WHEN 3 THEN 'Repeatable' 
    WHEN 4 THEN 'Serializable' 
    WHEN 5 THEN 'Snapshot' 
    ELSE 'Unknown' 
    END 
    FROM sys.dm_exec_sessions 
    where session_id = @@SPID 

    -- Return the result of the function 
    RETURN @til 

END 


GO 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE TABLE [dbo].[NameTable](
    [Id] [int] IDENTITY(1,1) NOT NULL, 
    [Name] [nvarchar](50) NOT NULL, 
    [Status] [int] NOT NULL, 
CONSTRAINT [PK_NameTable] PRIMARY KEY CLUSTERED 
(
    [Id] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

GO 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE TABLE [dbo].[SPLog](
    [Id] [int] IDENTITY(1,1) NOT NULL, 
    [StoredProcName] [nvarchar](50) NOT NULL, 
    [LogDate] [datetime] NOT NULL, 
CONSTRAINT [PK_SPLog] PRIMARY KEY CLUSTERED 
(
    [Id] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

GO 

单元测试

using System; 
using System.Data; 
using System.Data.SqlClient; 
using System.Threading; 
using System.Transactions; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 

namespace TransactionTest 
{ 
    [TestClass] 
    public class UnitTest1 
    { 
     [TestMethod] 
     public void SPGet_NoTransaction_ShouldReturn_ReadCommitted() 
     { 
      using (var con = new SqlConnection(GetConnection())) 
      { 
       con.Open(); 

       using (var cmd = new SqlCommand("usp_GetName", con)) 
       { 
        cmd.CommandType = CommandType.StoredProcedure; 
        AddParameter(cmd, "@id", 1, SqlDbType.Int); 
        AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output); 

        SqlDataReader reader = cmd.ExecuteReader(); 
        while (reader.Read()) 
        { 
        } 
        reader.Close(); 

        var transactionIsolation = (string)cmd.Parameters["@TransactionIsolation"].Value; 


        Assert.AreEqual("ReadCommitted", transactionIsolation); 
       } 
      } 
     } 

     [TestMethod] 
     public void SPUpdate_NoTransaction_ShouldReturn_ReadCommitted() 
     { 
      using (var con = new SqlConnection(GetConnection())) 
      { 
       con.Open(); 

       using (var cmd = new SqlCommand("usp_UpdateName", con)) 
       { 
        cmd.CommandType = CommandType.StoredProcedure; 
        AddParameter(cmd, "@id", 1, SqlDbType.Int); 
        AddParameter(cmd, "@name", "test update", SqlDbType.VarChar, 30); 
        AddParameter(cmd, "@status", 1, SqlDbType.Int); 
        AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output); 

        SqlDataReader reader = cmd.ExecuteReader(); 
        while (reader.Read()) 
        { 
        } 
        reader.Close(); 

        var transactionIsolation = (string)cmd.Parameters["@TransactionIsolation"].Value; 


        Assert.AreEqual("ReadCommitted", transactionIsolation); 
       } 
      } 
     } 

     [TestMethod] 
     public void SPGet_TransactionSerializable_ShouldReturn_Serializable() 
     { 
      using (var tran = new TransactionScope(
       TransactionScopeOption.Required, 
       new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.Serializable })) 
      { 
       using (var con = new SqlConnection(GetConnection())) 
       { 
        con.Open(); 

        using (var cmd = new SqlCommand("usp_GetName", con)) 
        { 
         cmd.CommandType = CommandType.StoredProcedure; 
         AddParameter(cmd, "@id", 1, SqlDbType.Int); 
         AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output); 

         SqlDataReader reader = cmd.ExecuteReader(); 
         while (reader.Read()) 
         { 
         } 
         reader.Close(); 

         var transactionIsolation = (string)cmd.Parameters["@TransactionIsolation"].Value; 


         Assert.AreEqual("Serializable", transactionIsolation); 
        } 
       } 
       tran.Complete(); 
      } 
     } 

     private static string GetConnection() 
     { 
      var builder = new SqlConnectionStringBuilder(); 
      builder.InitialCatalog = "ACMETransactions"; 
      builder.DataSource = "."; 
      builder.IntegratedSecurity = true; 
      builder.Pooling = true; 
      builder.MaxPoolSize = 100; 
      return builder.ToString(); 
     } 

     private static void AddParameter(SqlCommand command, string name, object value, SqlDbType type, int size = -1, ParameterDirection direction = ParameterDirection.Input) 
     { 
      command.Parameters.Add(new SqlParameter 
      { 
       ParameterName = name, 
       SqlDbType = type, 
       Value = value, 
       Direction = direction 
      }); 
      if (size != -1) 
      { 
       command.Parameters[command.Parameters.Count - 1].Size = size; 
      } 
     } 

     [TestMethod] 
     public void SPGet__MultiThread_Conflict() 
     { 
      string serializedIsolationResult = ""; 
      string normalIsolationResult = ""; 

      var normalThread = new Thread(new ThreadStart(() => 
      { 
       using (var con = new SqlConnection(GetConnection())) 
       { 
        con.Open(); 

        using (var cmd = new SqlCommand("usp_GetName", con)) 
        { 
         cmd.CommandType = CommandType.StoredProcedure; 
         AddParameter(cmd, "@id", 1, SqlDbType.Int); 
         AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output); 

         SqlDataReader reader = cmd.ExecuteReader(); 
         while (reader.Read()) ; 

         reader.Close(); 

         normalIsolationResult = (string)cmd.Parameters["@TransactionIsolation"].Value; 
        } 
       } 
      })); 

      var serializedThread = new Thread(new ThreadStart(() => 
      { 
       using (var tran = new TransactionScope(
        TransactionScopeOption.Required, 
        new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.Serializable })) 
       { 
        using (var con = new SqlConnection(GetConnection())) 
        { 
         con.Open(); 

         using (var cmd = new SqlCommand("usp_GetName", con)) 
         { 
          cmd.CommandType = CommandType.StoredProcedure; 
          AddParameter(cmd, "@id", 1, SqlDbType.Int); 
          AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output); 

          SqlDataReader reader = cmd.ExecuteReader(); 
          while (reader.Read()) ; 

          reader.Close(); 

          serializedIsolationResult = (string)cmd.Parameters["@TransactionIsolation"].Value; 
         } 

         normalThread.Join(); 
         Thread.Sleep(1000); 
        } 
        tran.Complete(); 
       } 
      })); 

      serializedThread.Start(); 
      normalThread.Start(); 

      serializedThread.Join(); 

      Assert.AreEqual("ReadCommitted", normalIsolationResult); 
      Assert.AreEqual("Serializable", serializedIsolationResult); 

     } 

     [TestMethod] 
     public void SPGet__MultiThread_NoTransactionScope() 
     { 
      string serializedIsolationResult = ""; 
      string normalIsolationResult = ""; 

      var normalThread = new Thread(new ThreadStart(() => 
      { 
       using (var con = new SqlConnection(GetConnection())) 
       { 
        con.Open(); 

        using (var cmd = new SqlCommand("usp_GetName", con)) 
        { 
         cmd.CommandType = CommandType.StoredProcedure; 
         AddParameter(cmd, "@id", 1, SqlDbType.Int); 
         AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output); 

         SqlDataReader reader = cmd.ExecuteReader(); 
         while (reader.Read()) ; 

         reader.Close(); 

         normalIsolationResult = (string)cmd.Parameters["@TransactionIsolation"].Value; 
        } 
       } 
      })); 

      var serializedThread = new Thread(new ThreadStart(() => 
      { 
       using (var con = new SqlConnection(GetConnection())) 
       { 
        con.Open(); 

        using (var cmd = new SqlCommand("usp_GetName", con)) 
        { 
         cmd.CommandType = CommandType.StoredProcedure; 
         AddParameter(cmd, "@id", 1, SqlDbType.Int); 
         AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output); 

         SqlDataReader reader = cmd.ExecuteReader(); 
         while (reader.Read()) ; 

         reader.Close(); 

         serializedIsolationResult = (string)cmd.Parameters["@TransactionIsolation"].Value; 
        } 
       } 
      })); 

      serializedThread.Start(); 
      normalThread.Start(); 

      serializedThread.Join(); 

      Assert.AreEqual("ReadCommitted", normalIsolationResult); 
      Assert.AreEqual("ReadCommitted", serializedIsolationResult); 

     } 

     [TestMethod] 
     public void SPGet__MultiConnection() 
     { 
      string normalIsolationResult2 = ""; 
      string normalIsolationResult1 = ""; 

      using (var con = new SqlConnection(GetConnection())) 
      { 
       con.Open(); 

       using (var cmd = new SqlCommand("usp_GetName", con)) 
       { 
        cmd.CommandType = CommandType.StoredProcedure; 
        AddParameter(cmd, "@id", 1, SqlDbType.Int); 
        AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output); 

        SqlDataReader reader = cmd.ExecuteReader(); 
        while (reader.Read()) ; 

        reader.Close(); 

        normalIsolationResult1 = (string)cmd.Parameters["@TransactionIsolation"].Value; 
       } 
      } 

      using (var con = new SqlConnection(GetConnection())) 
      { 
       con.Open(); 

       using (var cmd = new SqlCommand("usp_GetName", con)) 
       { 
        cmd.CommandType = CommandType.StoredProcedure; 
        AddParameter(cmd, "@id", 1, SqlDbType.Int); 
        AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output); 

        SqlDataReader reader = cmd.ExecuteReader(); 
        while (reader.Read()) ; 

        reader.Close(); 

        normalIsolationResult2 = (string)cmd.Parameters["@TransactionIsolation"].Value; 
       } 
      } 

      Assert.AreEqual("ReadCommitted", normalIsolationResult1); 
      Assert.AreEqual("ReadCommitted", normalIsolationResult2); 
     } 

     [TestMethod] 
     public void SPGet__SingleConnection() 
     { 
      string normalIsolationResult2 = ""; 
      string normalIsolationResult1 = ""; 

      using (var con = new SqlConnection(GetConnection())) 
      { 
       con.Open(); 

       using (var cmd = new SqlCommand("usp_GetName", con)) 
       { 
        cmd.CommandType = CommandType.StoredProcedure; 
        AddParameter(cmd, "@id", 1, SqlDbType.Int); 
        AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output); 

        SqlDataReader reader = cmd.ExecuteReader(); 
        while (reader.Read()) ; 

        reader.Close(); 

        normalIsolationResult1 = (string)cmd.Parameters["@TransactionIsolation"].Value; 
       } 

       using (var cmd = new SqlCommand("usp_GetName", con)) 
       { 
        cmd.CommandType = CommandType.StoredProcedure; 
        AddParameter(cmd, "@id", 1, SqlDbType.Int); 
        AddParameter(cmd, "@TransactionIsolation", 1, SqlDbType.VarChar, 30, ParameterDirection.Output); 

        SqlDataReader reader = cmd.ExecuteReader(); 
        while (reader.Read()) ; 

        reader.Close(); 

        normalIsolationResult2 = (string)cmd.Parameters["@TransactionIsolation"].Value; 
       } 
      } 

      Assert.AreEqual("ReadCommitted", normalIsolationResult1); 
      Assert.AreEqual("ReadCommitted", normalIsolationResult2); 
     } 
    } 


} 

简单地改变getConnection方法去除池/ MaxPoolSize导致检测每一次通过。

有了这些参数,一些测试将会失败。

我假设在带有死锁的实时环境中,我们正在目睹连接被重用,事务范围被设置为Serializable的其他地方,从代码执行任何更新,其中明确使用了TransactionScope。