Sql server 下面的代码不能导致死锁,但我得到的事务(进程ID 54)被死锁…

Sql server 下面的代码不能导致死锁,但我得到的事务(进程ID 54)被死锁…,sql-server,wcf,transactions,Sql Server,Wcf,Transactions,服务操作MyMethodint id根据单个DB表中的id参数检索特定行,并在返回之前将该状态保存回该表。如果两个调用第一个调用发生在事务T1中,而第二个调用发生在事务T2中,同时调用MyMethod,则服务将尝试同时执行这两个调用。由于T1和T2都试图访问同一个DB表,因此两个事务中的一个将被授予对资源的访问权限,而另一个事务将被阻止,直到原始事务提交或中止。但我得到的是一个异常事务,进程ID 54在另一个进程的锁资源上被死锁,并被选为死锁受害者 我不理解抛出死锁异常背后的原因,因为据我所知,

服务操作MyMethodint id根据单个DB表中的id参数检索特定行,并在返回之前将该状态保存回该表。如果两个调用第一个调用发生在事务T1中,而第二个调用发生在事务T2中,同时调用MyMethod,则服务将尝试同时执行这两个调用。由于T1和T2都试图访问同一个DB表,因此两个事务中的一个将被授予对资源的访问权限,而另一个事务将被阻止,直到原始事务提交或中止。但我得到的是一个异常事务,进程ID 54在另一个进程的锁资源上被死锁,并被选为死锁受害者

我不理解抛出死锁异常背后的原因,因为据我所知,死锁没有任何危险。首先,这两个事务在不同的行上访问和操作。为什么在原始事务提交或中止之前DB资源没有被锁定

代码如下:

[ServiceContract]
public interface IService
{
    [OperationContract]
    [TransactionFlow(TransactionFlowOption.Allowed)]
    void Process(int id);
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, IncludeExceptionDetailInFaults=true)]
public class Service : IService
{
    string state_Data = "";

    [OperationBehavior(TransactionScopeRequired = true)]
    public void Process(int id)
    {
        GetState(id);
        Thread.Sleep(6000);
        SaveState(id);
    }


    private void GetState(int id)
    {
        using (SqlConnection con = new SqlConnection())
        {
            con.ConnectionString = "data source=localhost; initial catalog=WCF; integrated security=sspi;";

            SqlCommand cmd = new SqlCommand();
            cmd.CommandText = "SELECT * FROM StateTable WHERE id = @id";
            cmd.Parameters.Add("@id", SqlDbType.Int).Value = id;
            cmd.Connection = con;
            con.Open();

            SqlDataReader reader = cmd.ExecuteReader();
            if (reader.Read())
                state_Data = reader["State"].ToString();
        }
    }

    private bool SaveState(int id)
    {
        using (SqlConnection con = new SqlConnection())
        {
            con.ConnectionString = "data source = localhost; initial catalog=WCF; integrated security=sspi;";

            SqlCommand cmd = new SqlCommand();
            cmd.CommandText = "UPDATE StateTable SET State=@State WHERE Id = @id";
            cmd.Parameters.Add("@id", SqlDbType.Int).Value = id;
            cmd.Parameters.Add("@State", SqlDbType.NVarChar).Value = state_Data;
            cmd.Connection = con;
            con.Open();

            int ret = cmd.ExecuteNonQuery();
            return ret == 1;
        }
    }
}
编辑:

如果这有帮助,以下是客户端代码:

第一个客户:

        ServiceClient proxy = new ServiceClient("WSDualHttpBinding_IService");

        using (TransactionScope scope = new TransactionScope())
        {
            proxy.Process(1);
            scope.Complete();
        }
第二个客户:

        ServiceClient proxy = new ServiceClient("WSDualHttpBinding_IService");

        using (TransactionScope scope = new TransactionScope())
        {
            proxy.Process(2);
            scope.Complete();
        }
谢谢

实际上,这段代码是一个有保证的死锁。在同一ID上可以有任意数量的成功GetState调用,所有调用都会成功,因为它们都是通过可序列化事务作用域共享锁获取和保留的,因此是兼容的。由于大量共享锁与更新所需的X锁不兼容,因此任何后续保存状态的尝试都将被阻止。下一个保存状态将死锁。保证每次100%重新编程

如果您关心性能,则应该使用。如果性能不相关,那么GetState应该以独占方式锁定状态,例如通过提供提示

当然,我假设StateTable中的ID上有一个聚集索引。

实际上,这段代码是一个有保证的死锁。在同一ID上可以有任意数量的成功GetState调用,所有调用都会成功,因为它们都是通过可序列化事务作用域共享锁获取和保留的,因此是兼容的。由于大量共享锁与更新所需的X锁不兼容,因此任何后续保存状态的尝试都将被阻止。下一个保存状态将死锁。保证每次100%重新编程

如果您关心性能,则应该使用。如果性能不相关,那么GetState应该以独占方式锁定状态,例如通过提供提示


当然,我假设StateTable中的ID上有一个聚集索引。

1 T1请求并被授予S锁。2 T2请求S锁,与T1持有的现有S锁兼容,因此被授予。3 T1请求X锁。这与在步骤2中授予T2的S锁不兼容,因此它会等待。4 T2请求X锁,它与步骤1中授予T1的S锁不兼容,因此它等待。死锁:T1在第3步的T2上等待,T2在第4步的T1上等待。在第1步和第2步授予的S锁在事务期间保持的事实是由OperationBehavior事务作用域引起的。不允许立即释放S锁。这将消除死锁,但您的应用程序将假定它读取的状态是最后一个状态,T2将简单地覆盖T1更改丢失的更新。@cyberkiwi:如果ID不同,则由于哈希冲突,您只有死锁的可能性:。但是如果ID不同,那么你根本不需要任何锁定。唯一有趣的正确性案例是当两个请求试图修改相同的id时。@remus-如果id不同-这还会死锁吗?OP可能是错误的,但确实声明它们将在不同的ID上操作,我正在使用Level Serializable进行测试,而不使用TABLOCK-OperationBehavior TransactionScopeRequired是否等同于WITHTABLOCK?@remus我试图编辑,但无法,因此delete添加了修改后的注释。我读了你的好文章。我只是想知道,因为你说保证死锁1 T1请求并被授予S锁。2 T2请求S锁,与T1持有的现有S锁兼容,因此被授予。3 T1请求X锁。这与在步骤2中授予T2的S锁不兼容,因此它会等待。4 T2请求X锁,它与步骤1中授予T1的S锁不兼容,因此它等待。死锁:T1在第3步的T2上等待,T2在第4步的T1上等待。在第1步和第2步授予的S锁在事务期间保持的事实是由OperationBehavior事务作用域引起的。不允许立即释放S锁。这将消除死锁,但您的应用程序将假定它读取的状态是最后一个状态,T2将简单地覆盖T1更改丢失的更新。@cyberkiwi:如果ID不同,则您有
a由于哈希冲突,只有死锁的可能性:。但是如果ID不同,那么你根本不需要任何锁定。唯一有趣的正确性案例是当两个请求试图修改相同的id时。@remus-如果id不同-这还会死锁吗?OP可能是错误的,但确实声明它们将在不同的ID上操作,我正在使用Level Serializable进行测试,而不使用TABLOCK-OperationBehavior TransactionScopeRequired是否等同于WITHTABLOCK?@remus我试图编辑,但无法,因此delete添加了修改后的注释。我读了你的好文章。我只是想知道,因为你说的是保证死锁对不起,我没有注意到你的问题。我对索引一无所知,但我怀疑表是否有索引,因为创建表的sql语句没有指定任何索引-CREATE table StateTable id int PRIMARY KEY,State NVARCHAR100对不起,我没有注意到您的问题。我对索引一无所知,但我怀疑表是否有索引,因为创建表的sql语句并没有指定任何索引—CREATETABLE StateTable id int PRIMARKEY,State nvarchar100