Почему возникает тупик? - PullRequest
       46

Почему возникает тупик?

6 голосов
/ 08 августа 2011

Я использую небольшую транзакцию, которая состоит из двух простых запросов: выберите и обновите:

SELECT * FROM XYZ WHERE ABC = DEF

и

UPDATE XYZ SET ABC = 123
WHERE ABC = DEF

Нередко возникает ситуация, когда транзакция запускается двумя потоками и в зависимости от уровня блокировки происходит взаимоблокировка (RepeatableRead, Serialization). Обе транзакции пытаются прочитать и обновить одну и ту же строку. Мне интересно, почему это происходит. Какой порядок запросов приводит к тупику? Я немного читал о блокировке (разделяемой, эксклюзивной) и продолжительности блокировок для каждого уровня изоляции, но я до сих пор не до конца понимаю ...

Я даже подготовил простой тест, который всегда приводит к тупику. Я посмотрел на результаты теста в SSMS и SQL Server Profiler. Я начал первый запрос, а затем сразу второй.

Первый запрос:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT ...
WAITFOR DELAY '00:00:04'
UPDATE ...
COMMIT

Второй запрос:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT ...
UPDATE ...
COMMIT

Теперь я не могу показать вам подробные журналы, но выглядит это примерно так (скорее всего, я где-то пропустил Lock: deadlock и т. Д.):

(1) SQL:BatchStarting: First query
(2) SQL:BatchStarting: Second query
(3) Lock:timeout for second query
(4) Lock:timeout for first query
(5) Deadlock graph

Если я хорошо понимаю блокировки, в (1) первый запрос принимает общую блокировку (для выполнения SELECT), затем переходит в спящий режим и сохраняет общую блокировку до конца транзакции. В (2) второй запрос также принимает общую блокировку (SELECT), но не может принимать эксклюзивную блокировку (UPDATE), пока в той же строке есть общие блокировки, что приводит к Lock: timeout. Но я не могу объяснить, почему происходит тайм-аут для второго запроса. Возможно, я не совсем понимаю весь процесс. Кто-нибудь может дать хорошее объяснение?

Я не заметил взаимоблокировок с использованием ReadCommitted, но боюсь, что они могут возникнуть. Какое решение вы рекомендуете?

Ответы [ 4 ]

5 голосов
/ 08 августа 2011

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

http://msdn.microsoft.com/en-us/library/ms177433.aspx

3 голосов
/ 09 августа 2011

Для MSSQL существует механизм предотвращения тупиков. То, что вам нужно здесь, называется WITH NOLOCK подсказка.

В 99,99% случаев SELECT операторов это можно использовать, и нет необходимости связывать SELECT с ОБНОВЛЕНИЕМ. Также нет необходимости помещать SELECT в транзакцию. Единственное исключение - когда не разрешено грязное чтение.

Изменение ваших запросов на эту форму решит все ваши проблемы:

SELECT ...
FROM yourtable WITH (NOLOCK)
WHERE ...

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
UPDATE ...
COMMIT
3 голосов
/ 08 августа 2011

"Но я не могу объяснить, почему происходит тайм-аут для второго запроса."

Потому что первый запрос содержит общую блокировку. Затем обновление в первом запросе также пытается получить эксклюзивную блокировку, которая заставляет его спать. Таким образом, первый и второй запросы спят, ожидая, пока другой проснется - и это тупик, который приводит к таймауту: -)

В MySQL это работает лучше - тупик обнаружен немедленно, и одна из транзакций откатывается (вам не нужно ждать таймаута: -)).

Кроме того, в mysql вы можете сделать следующее, чтобы предотвратить тупик :

select ... for update

, который устанавливает блокировку записи (то есть монопольную блокировку) с самого начала транзакции, и таким образом вы избежите тупиковой ситуации! Возможно, вы можете сделать что-то похожее в своей базе данных.

1 голос
/ 09 августа 2011

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

Оператор обновления требует монопольной блокировки, и, следовательно, оператор обновления должен ожидать снятия блокировки чтения.

Ни одна из двух транзакций не снимет блокировки, поэтому транзакции завершатся неудачей.

Разные реализации баз данных имеют разные стратегии для решения этой проблемы: Sybase и MS-SQL-серверы используют эскалацию блокировки с тайм-аутом (эскалация от блокировки на чтение-запись) - Oracle, я считаю (в какой-то момент) реализована согласованность чтения с использованием журнала отката, в котором MySQL имеет другую стратегию.

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