Я использую небольшую транзакцию, которая состоит из двух простых запросов: выберите и обновите:
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, но боюсь, что они могут возникнуть.
Какое решение вы рекомендуете?