2009-06-06 8 views
7

У меня проблема с тупиком на SELECT/UPDATE на SQL Server 2008. Я читал ответы из этой темы: SQL Server deadlocks between select/update or multiple selects, но я до сих пор не понимаю, почему я зашел в тупик.Deadlock on SELECT/UPDATE

Я воссоздал ситуацию в следующей тестовой таблице.

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

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. 

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

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

Спасибо заранее.

+0

+1 для хорошо документированного вопроса! –

ответ

4

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

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

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

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

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

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

+0

Это сработало :) Можно ли достичь этого с помощью linq2sql? – empi

+0

Похоже, что нет ... http://stackoverflow.com/questions/806775/linq-to-sql-with-updlock –

+0

Вчера я попробовал (REPEATABLEREAD), потому что я читал, что это эквивалентно SELECT FOR UPDATE, но это не работал. Как я уже сказал, обломок сделал трюк. Я думаю, что я открою новый вопрос о updlock в linq2sql. Спасибо за ответ. Это придало мне серьезную головную боль;) – empi