Тупик при ВЫБРАТЬ / ОБНОВИТЬ - PullRequest
7 голосов
/ 06 июня 2009

У меня проблема с взаимоблокировкой при SELECT / UPDATE на SQL Server 2008. Я читаю ответы из этой темы: тупики SQL Server между выбором / обновлением или множественным выбором , но я все еще не понимаю, почему я получаю тупик.

Я воссоздал ситуацию в следующем тестовом примере.

У меня есть таблица:

CREATE TABLE [dbo].[SessionTest](
    [SessionId] UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL,
    [ExpirationTime] DATETIME NOT NULL,
    CONSTRAINT [PK_SessionTest] PRIMARY KEY CLUSTERED (
        [SessionId] 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

ALTER TABLE [dbo].[SessionTest] 
    ADD CONSTRAINT [DF_SessionTest_SessionId] 
    DEFAULT (NEWID()) FOR [SessionId]
GO

Сначала я пытаюсь выбрать запись из этой таблицы и, если запись существует, установить время истечения до текущего времени плюс некоторый интервал. Это достигается с помощью следующего кода:

protected Guid? GetSessionById(Guid sessionId, SqlConnection connection, SqlTransaction transaction)
{
    Logger.LogInfo("Getting session by id");
    using (SqlCommand command = new SqlCommand())
    {
        command.CommandText = "SELECT * FROM SessionTest WHERE SessionId = @SessionId";
        command.Connection = connection;
        command.Transaction = transaction;
        command.Parameters.Add(new SqlParameter("@SessionId", sessionId));

        using (SqlDataReader reader = command.ExecuteReader())
        {
            if (reader.Read())
            {
                Logger.LogInfo("Got it");
                return (Guid)reader["SessionId"];
            }
            else
            {
                return null;
            }
        }
    }
}

protected int UpdateSession(Guid sessionId, SqlConnection connection, SqlTransaction transaction)
{
    Logger.LogInfo("Updating session");
    using (SqlCommand command = new SqlCommand())
    {
        command.CommandText = "UPDATE SessionTest SET ExpirationTime = @ExpirationTime WHERE SessionId = @SessionId";
        command.Connection = connection;
        command.Transaction = transaction;
        command.Parameters.Add(new SqlParameter("@ExpirationTime", DateTime.Now.AddMinutes(20)));
        command.Parameters.Add(new SqlParameter("@SessionId", sessionId));
        int result = command.ExecuteNonQuery();
        Logger.LogInfo("Updated");
        return result;
    }
}

public void UpdateSessionTest(Guid sessionId)
{
    using (SqlConnection connection = GetConnection())
    {
        using (SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable))
        {
            if (GetSessionById(sessionId, connection, transaction) != null)
            {
                Thread.Sleep(1000);
                UpdateSession(sessionId, connection, transaction);
            }
            transaction.Commit();
        }
    }
}

Затем, если я пытаюсь выполнить метод теста из двух потоков и они пытаются обновить одну и ту же запись, я получаю следующий вывод:

[4] : Creating/updating session
[3] : Creating/updating session
[3] : Getting session by id
[3] : Got it
[4] : Getting session by id
[4] : Got it
[3] : Updating session
[4] : Updating session
[3] : Updated
[4] : Exception: Transaction (Process ID 59) was deadlocked 
on lock resources with another process and has been 
chosen as the deadlock victim. Rerun the transaction.

Я не могу понять, как это может произойти, используя Сериализуемый уровень изоляции. Я думаю, что первый выбор должен заблокировать строку / таблицу и не позволит другому выбрать для получения каких-либо блокировок. Пример написан с использованием командных объектов, но он только для целей тестирования. Первоначально я использую linq, но я хотел показать упрощенный пример. Sql Server Profiler показывает, что взаимоблокировка - это блокировка ключа. Я обновлю вопрос через несколько минут и опубликую график с сервера профилирования SQL. Любая помощь будет оценена. Я понимаю, что решением этой проблемы может быть создание критического раздела в коде, но я пытаюсь понять, почему Serializable Isolation Level не помогает.

А вот график тупиковой ситуации: тупик http://img7.imageshack.us/img7/9970/deadlock.gif

Заранее спасибо.

1 Ответ

4 голосов
/ 06 июня 2009

Недостаточно иметь сериализуемую транзакцию, вам нужно намечь на блокировку, чтобы это работало.

Сериализуемый уровень изоляции по-прежнему обычно приобретает «самый слабый» тип блокировки, который он может обеспечить, что обеспечивает выполнение условий сериализации (повторяющиеся чтения, отсутствие фантомных строк и т. Д.)

Итак, вы захватываете общую блокировку на своей таблице, которую вы позже (в вашей сериализуемой транзакции) пытаетесь обновить до блокировки обновления. Обновление завершится неудачно, если другой поток удерживает общую блокировку (это будет работать, если нет другого тела, которое удерживает общую блокировку).

Возможно, вы захотите изменить его на следующее:

SELECT * FROM SessionTest with (updlock) WHERE SessionId = @SessionId

Это обеспечит получение блокировки обновления при выполнении SELECT (поэтому вам не нужно будет обновлять блокировку).

...