График взаимоблокировок в профилировщике SQL-сервера показывает взаимную блокировку одного и того же кластерного ключа

Я пытаюсь определить причину тупика с помощью SQL Server Profiler. Вот график взаимоблокировок: Deadlock graphОба оператора являются вставками, за которыми следует select scope_identity(); Фактически имеют 2 параллельных процесса, которые многократно выполняют insert-select_identity за один цикл.

Я ожидал, что insert принимает исключительную блокировку для кластеризованного индекса, а select принимает общую блокировку некластеризованного индекса , а затем ждут, пока друг друга освободят свои индексы.

Я вижу, что оба процесса ждут освобождения одного и того же ресурса - кластеризованного индекса. Как это может быть? Конкретное обращение должно принадлежать либо одному процессу, либо другому. Что мне здесь не хватает? заранее всем спасибо.

Отредактировано: да, уровень изоляции - Serializible. PS: вероятно, мое предположение об общей блокировке некластеризованного индекса было неверным, поскольку мой выбор не содержит оператора where

Edit2: вот часть xml:

 <resource-list>
   <keylock hobtid="72057594148028416" dbid="29" objectname="<confidential>" indexname="PK_WP_Inbound_StockTransactionLine" id="lock9641700" mode="RangeS-S" associatedObjectId="72057594148028416">
    <owner-list>
     <owner id="process8e09288" mode="RangeS-S"/>
    </owner-list>
    <waiter-list>
     <waiter id="process991ce08" mode="RangeI-N" requestType="convert"/>
    </waiter-list>
   </keylock>
   <keylock hobtid="72057594148028416" dbid="29" objectname="<confidential>" indexname="PK_WP_Inbound_StockTransactionLine" id="lock9641700" mode="RangeS-S" associatedObjectId="72057594148028416">
    <owner-list>
     <owner id="process991ce08" mode="RangeS-S"/>
    </owner-list>
    <waiter-list>
     <waiter id="process8e09288" mode="RangeI-N" requestType="convert"/>
    </waiter-list>
   </keylock>
  </resource-list>

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


person Artur Udod    schedule 25.09.2012    source источник
comment
Какой уровень изоляции вы используете? сериализуемый? Если да, то какая для этого причина?   -  person Martin Smith    schedule 25.09.2012
comment
да, я забыл это сказать, извините. Я отредактировал сообщение   -  person Artur Udod    schedule 25.09.2012
comment
публиковать тупиковый XML, а не изображение. Изображение неполное, вводящее в заблуждение и часто ошибочное.   -  person Remus Rusanu    schedule 25.09.2012
comment
Вы определенно имеете дело с разными строками в кластеризованном индексе. Отправьте полный XML.   -  person Roji P Thomas    schedule 25.09.2012


Ответы (1)


Представьте, что следующий код вызывается из двух параллельных транзакций (T1 и T2), обращающихся к одной и той же записи.

Read LastRow
Insert AtLastRow

Допустим, переключение контекста происходит в Read. Итак, последовательность операций

T1 Read LastRow
T2 Read LastRow
T2 Insert AtLastRow // This will wait for T1 to finish.
T1 Insert AtLastRow // This will wait for T2 to finish. Hence deadlock!

Выше прочитанное займет Range S-S блокировку. Наконец, Insert также нуждается в Range I-N, что несовместимо с существующей блокировкой Range S-S, удерживаемой другими транзакциями. Следовательно, он ждет.

Есть несколько способов решить эту проблему.

  1. Используйте прочитанное подтверждено как общий уровень изоляции и не сериализуемый. Это предотвратит взятие блокировки диапазона.
  2. Чтение с блокировкой обновления (UPDLOCK). Это займет 1-е место эксклюзивной блокировкой обновления. Следовательно, другая транзакция будет ждать самого чтения.
  3. Избегайте шаблонов чтения и вставки / обновления. Просто продолжайте вставлять / обновлять и пусть это не удастся.

Дайте знать, если у вас появятся вопросы.

person Ankush    schedule 26.09.2012
comment
3. Избегайте шаблонов чтения и вставки / обновления. Тогда как мне справиться с неудачами? т.е. мне нужно сделать что-то еще (например, отобразить конкретное сообщение) в случае, если запись уже существует. Должен ли я просто w8 для исключения? и что потом? проанализировать данные исключения, чтобы определить его точную причину? Кроме того, почему не удается вставить / обновить? Рассмотрим ситуацию, когда мне нужно проверить ограничения бизнес-логики, а не ограничения модели данных. Возможно, решение также состоит в том, чтобы убрать опцию чтения из транзакции. Но в нашем случае это двухкратный рефакторинг, поэтому я изменю уровень изоляции в качестве обходного пути. спасибо =) - person Artur Udod; 26.09.2012
comment
@ArturUdod позволяет сказать, что вы читаете строку с ключом = 10 и увеличиваете значение. Вы можете либо прочитать значение, а затем обновить его, либо продолжить и обновить. Если ключ не существует, обновление не будет выполнено. Теперь, если вы обнаружите сбой, вы вставляете ключ 10 со значением 1. Это помогает? - person Ankush; 26.09.2012
comment
Да, я получил его. Но, как я уже сказал: предположим, что я хочу увеличивать значение только в том случае, если оно меньше 100. Если оно уже равно 100, то я хочу вывести сообщение. Вне курса может быть контрольное ограничение, но это простой пример. У меня может быть гораздо более сложная бизнес-логика проверки, и мне действительно нужно выполнить чтение перед изменением данных. - person Artur Udod; 26.09.2012
comment
@ArturUdod в этом случае выберите второй вариант. Чтение с блокировкой обновления (UPDLOCK). - person Ankush; 26.09.2012
comment
Собственно, в моем случае опасности не будет, если я переключу уровень изоляции на read-commited только для этого конкретного случая. Фантомные чтения маловероятны (просто потому, что логика сервера этому препятствует). Благодарю за ваш ответ. - person Artur Udod; 26.09.2012