以下代码不会导致死锁,但我得到的事务(进程ID 54)已经死锁...

时间:2011-03-23 20:47:36

标签: sql-server wcf transactions

服务操作MyMethod(int id)从单个数据库表中检索特定行(基于id参数),在它返回之前,它还将该状态保存回表中。如果两次通话(第一次通话发生在交易中 T1 ,而第二次发生在交易 T2 内)同时发送到MyMethod(),则服务将尝试执行同时打两个电话。由于 T1 T2 都尝试访问相同的数据库表,因此两个事务中的一个将被授予对资源的访问权限,而其他事务将被阻止,直到原始事务提交或中止。但相反,我得到一个异常事务(进程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();
        }

谢谢

1 个答案:

答案 0 :(得分:4)

实际上这段代码是保证死锁。所有成功的GetState调用都可以在同一个ID上成功完成,因为它们都获取(并且由于可序列化的事务范围而保留)共享锁,因此是兼容的。对SaveState的任何后续尝试都将阻止,因为大量共享锁都与更新所需的X锁不兼容。下一个SaveState将陷入僵局。 100%repro,保证,每次。

如果您关心效果,则应使用optimistic concurrency。如果性能无关紧要,那么GetState应该专门锁定状态,例如。通过提供XLOCK提示。

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