简体   繁体   English

SQL事务隔离级别可序列化与在开发与生产中提交的读取

[英]SQL Transaction Isolation Level Serializable vs Read Committed in Dev vs Production

I'm currently hunting down an issue we have with deadlocks and SQL Server. 我目前正在寻找死锁和SQL Server的问题。 There are quite a lot of deadlocks occurring. 发生了很多死锁。 The following deadlock graph is what I am focusing on (from the live environment): 以下死锁图是我关注的重点(来自实时环境):

<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="213210603" 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="213210603" 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="213210579" 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="213210579" 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> 

Looking at deadlock victim process id = process41fce08, which is a select stored procedure, appears to run under a Serializable transaction. 查看死锁受害者进程ID = process41fce08(这是一个选择的存储过程),它似乎在Serializable事务下运行。

However, looking at the stack trace, the transaction scope is not used. 但是,查看堆栈跟踪时,未使用事务作用域。 No explicit transaction is created. 没有创建显式事务。 A simple SqlConnection and SqlCommand is used to execute the stored procedure. 一个简单的SqlConnection和SqlCommand用于执行存储过程。

Looking at the call stack to execute the same code path on my local development machine this shows the transaction isolation level running under ReadCommitted . 查看调用堆栈以在本地开发计算机上执行相同的代码路径,这显示了在ReadCommitted下运行的事务隔离级别。 This information is retrieved using: 使用以下方法检索此信息:

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

from inside the stored procedure. 从存储过程内部。 If I call: 如果我打电话给:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

in the stored procedure, I see the isolation level change in the above select. 在存储过程中,我在上面的选择中看到隔离级别发生了变化。 This is what I expect and what I see in my development environment: 这是我的期望,也是我在开发环境中看到的:

Default Isolation Level in ADO.NET ADO.NET中的默认隔离级别

So given that: 因此,鉴于:

  1. TransactionScope does not get set in the call stack 未在调用堆栈中设置TransactionScope
  2. No isolation level is set inside the stored procedure 在存储过程中未设置隔离级别

Something appears to be setting the Isolation Level on the live environment? 似乎在现场环境中设置了隔离级别? The question is what is setting this or forcing it to be Serializable? 问题是是什么设置或强制使其可序列化?

The code runs in a MVC3 Application on .NET 4. 该代码在.NET 4的MVC3应用程序中运行。

It would appear to be down to Connection Pooling. 它似乎取决于连接池。

I noticed in the connection string we had the following: 我在连接字符串中注意到以下内容:

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

I created a number of unit tests to demonstrate this. 我创建了许多单元测试来证明这一点。

SQL Create Script 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

Unit Tests 单元测试

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);
        }
    }


}

Simply changing the GetConnection method to remove the Pooling/MaxPoolSize causes the tests to pass every time. 只需更改GetConnection方法以删除Pooling / MaxPoolSize即可使测试每次通过。

With these parameters some tests will fail. 使用这些参数,某些测试将失败。

I am assuming that in the live environment with the deadlocks, we are witnessing connections being reused with the transaction scope being set to Serializable elsewhere from code that executes any updates where TransactionScope is explicitly used. 我假设在具有死锁的实时环境中,我们正在见证连接被重用,并且在执行任何显式使用TransactionScope的更新的代码中,将事务作用域设置为Serializable。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 读取并提交了具有无锁和事务隔离级别的SQL事务 - SQL Transactions with nolocks and TRANSACTION ISOLATION LEVEL READ COMMITTED WITH(NOLOCK)与SET事务隔离级别的读取未提交 - WITH (NOLOCK) vs SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED SELECT语句 - 具有SET TRANSACTION ISOLATION LEVEL READ NOUOCK的NOLOCK - SELECT Statement - NOLOCK with SET TRANSACTION ISOLATION LEVEL READ COMMITTED NOLOCK与事务隔离级别 - NOLOCK vs. Transaction Isolation Level 具有可序列化隔离级别的事务中的死锁 - Deadlock in transaction with isolation level serializable 事务级别之间的差异:读可写和可隔离级别 - Differences between TRANSACTION's levels: READ WRITE and ISOLATION LEVEL SERIALIZABLE TRANSACTION ISOLATION LEVEL SERIALIZABLE是否创建READ锁定 - Does TRANSACTION ISOLATIoN LEVEL SERIALIZABLE create READ lock 归因于隔离级别可序列化的SQL事务失败的一般处理 - Generalized handling of SQL transaction failures due to isolation level serializable Sql事务中的隔离级别 - Isolation level in Sql Transaction SELECT可以使用Read Committed Isolation Level在一个事务中获得不同的结果吗? - Can SELECT get different results within one transaction using Read Committed Isolation Level?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM