Следующий код не может вызвать взаимоблокировку, и все же я получаю, что транзакция (ID процесса 54) была заблокирована ... - PullRequest
1 голос
/ 23 марта 2011

Сервисная операция MyMethod(int id) извлекает определенную строку (на основе параметра id) из одной таблицы БД и непосредственно перед ее возвратом сохраняет это состояние обратно в таблицу.Если два вызова (первый вызов происходит в транзакции T1 , а второй в транзакции T2 ) на MyMethod() сделаны одновременно, то служба попытается выполнить два вызова одновременно.Поскольку и T1 , и T2 пытаются получить доступ к одной и той же таблице БД, одной из двух транзакций будет предоставлен доступ к ресурсу, в то время как другая должна быть заблокирована до тех пор, пока исходная транзакция не завершится или не прекратит работу.Но вместо этого я получаю исключение Транзакция (ID процесса 54) была заблокирована для ресурсов блокировки другого процесса и была выбрана в качестве жертвы тупика

Я не понимаю причины, по которой возникает исключение взаимоблокировкипоскольку, насколько я могу судить, опасности тупика нет.С одной стороны, две транзакции доступны и работают в разных строках.Почему вместо этого ресурс БД не был заблокирован до тех пор, пока исходная транзакция не была зафиксирована или прервана ?!

Вот код:

[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 Ответ

4 голосов
/ 23 марта 2011

На самом деле этот код является гарантированным тупиком. На одном и том же ID может быть любое количество успешных вызовов GetState, причем все они успешно выполняются, поскольку все они получают (и сохраняют, из-за объема сериализуемой транзакции) общие блокировки и, следовательно, являются совместимыми. Любая последующая попытка SaveState будет заблокирована, поскольку множество общих блокировок несовместимы с блокировкой X, необходимой для обновления. Следующее SaveState будет в тупике. 100% репро, гарантировано, каждый раз.

Вместо этого следует использовать оптимистичный параллелизм , если вы заботитесь о производительности. Если производительность не имеет значения, GetState должен блокировать только состояние, например. предоставив подсказку XLOCK .

Конечно, я предполагаю, что в StateTable есть кластеризованный индекс на ID.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...