Недавно одно из наших приложений ASP.NET показало ошибку взаимоблокировки базы данных, и меня попросили проверить и исправить ошибку. Мне удалось найти причину тупика - хранимой процедуры, которая строго обновляла таблицу внутри курсора.
Я впервые вижу эту ошибку и не знаю, как ее эффективно отследить и исправить. Я перепробовал все возможные способы и, наконец, обнаружил, что обновляемая таблица не имеет первичного ключа! К счастью, это был столбец идентификации.
Позже я обнаружил, что разработчик, который написал сценарий для базы данных, запутался. Я добавил первичный ключ, и проблема была решена.
Я чувствовал себя счастливым и вернулся к своему проекту, и провел некоторое исследование, чтобы выяснить причину этого тупика ...
По-видимому, это было круглое условие ожидания, которое вызвало тупик. Обновления, по-видимому, занимают больше времени без первичного ключа, чем с первичным ключом.
Я знаю, что это не очень четкий вывод, поэтому я публикую здесь ...
- Проблема с отсутствующим первичным ключом?
- Существуют ли другие условия, которые вызывают взаимоблокировку, кроме (взаимное исключение, удержание и ожидание, отсутствие прерывания и циклическое ожидание)?
- Как мне предотвратить и отследить тупики?
источник
Ответы:
Отслеживание тупиков проще всего:
Профилактика является более сложной, по сути, вы должны следить за следующим:
Кодовый блок 1 блокирует ресурс A, а затем ресурс B в этом порядке.
Кодовый блок 2 блокирует ресурс B, а затем ресурс A в этом порядке.
Это классическое условие, при котором может возникнуть тупик, если блокировка обоих ресурсов не является атомарной, кодовый блок 1 может заблокировать A и получить приоритет, тогда кодовый блок 2 блокирует B, прежде чем A вернет время обработки. Теперь у вас тупик.
Чтобы предотвратить это состояние, вы можете сделать что-то вроде следующего
Кодовый блок A (псевдо-код)
Кодовый блок B (псевдокод)
не забывая разблокировать A и B, когда закончите с ними
это предотвратит взаимоблокировку между блоком кода A и блоком кода B
С точки зрения базы данных, я не уверен, как избежать этой ситуации, так как блокировки обрабатываются самой базой данных, то есть блокировки строк / таблиц при обновлении данных. Там, где я видел, чаще всего возникают проблемы, где вы видели свои, внутри курсора. Курсоры, как известно, неэффективны, избегайте их, если это вообще возможно.
источник
Мои любимые статьи для чтения и изучения взаимоблокировок: Simple Talk - отслеживание взаимоблокировок и SQL Server Central - Использование Profiler для устранения взаимоблокировок . Они дадут вам образцы и советы о том, как справиться с ситуацией.
Короче говоря, чтобы решить текущую проблему, я бы сократил вовлеченные транзакции, вынул из них ненужную часть, позаботился о порядке использования объектов, посмотрел, какой уровень изоляции действительно необходим, а не прочитал ненужные. данные...
Но лучше читайте статьи, они будут намного лучше в советах.
источник
Иногда тупиковая ситуация может быть устранена путем добавления индексации, так как она позволяет базе данных блокировать отдельные записи, а не всю таблицу, что снижает вероятность конфликтов и вероятность возникновения проблем.
Например, в InnoDB :
Другое распространенное решение - отключить согласованность транзакций, когда это не нужно, или иным образом изменить уровень изоляции , например, длительное задание для вычисления статистики ... обычно достаточно точного ответа, вам не нужны точные числа, как они меняются из-под тебя. И если это займет 30 минут, вам не нужно останавливать все другие транзакции в этих таблицах.
...
Что касается их отслеживания, это зависит от программного обеспечения базы данных, которое вы используете.
источник
Просто разработать на курсоре вещь. это действительно очень плохо. Он блокирует всю таблицу, а затем обрабатывает строки одну за другой.
Лучше проходить строки в виде курсора, используя цикл while
В цикле while выбор будет выполняться для каждой строки в цикле, и блокировка будет происходить только для одной строки за раз. Остальные данные в таблице бесплатны для запросов, что снижает вероятность возникновения тупиковых ситуаций.
Плюс это быстрее. Заставляет задуматься, почему в любом случае есть курсоры.
Вот пример такой структуры:
Если ваше поле идентификатора невелико, вы можете выбрать отдельный список идентификаторов и повторить его:
источник
Отсутствие первичного ключа не является проблемой. По крайней мере, само собой. Во-первых, вам не нужен первичный, чтобы иметь индексы. Во-вторых, даже если вы выполняете сканирование таблицы (что должно произойти, если ваш конкретный запрос не использует индекс, блокировка таблицы сама по себе не вызовет взаимоблокировку. Процесс записи будет ожидать чтения, а процесс чтения - ждать записи, и, конечно, чтения не придется ждать друг друга вообще.
В дополнение к другим ответам, уровень изоляции транзакции имеет значение, потому что повторяемое чтение и сериализация - это то, что вызывает удержание блокировок «чтение» до конца транзакции. Блокировка ресурса не вызывает тупик. Держать его под замком делает. Операции записи всегда держат свой ресурс заблокированным до конца транзакции.
Моя любимая стратегия предотвращения блокировок - использовать функции «снимка». Функция Read Committed Snapshot означает, что чтение не использует блокировки! И если вам нужно больше контроля, чем «Зафиксировано чтение», есть функция «Уровень изоляции моментального снимка». Это позволяет выполнять сериализованную (с использованием здесь терминов MS) транзакцию, не блокируя других игроков.
Наконец, один класс взаимоблокировок можно предотвратить с помощью блокировки обновления. Если вы читаете и удерживаете чтение (HOLD, или с помощью Repeatable Read), и другой процесс делает то же самое, тогда оба пытаются обновить одни и те же записи, у вас будет тупик. Но если оба запрашивают блокировку обновления, второй процесс будет ожидать первого, одновременно позволяя другим процессам читать данные с помощью общих блокировок, пока данные не будут фактически записаны. Это, конечно, не будет работать, если один из процессов все еще запрашивает общую блокировку HOLD.
источник
В то время как курсоры работают медленно в SQL Server, вы можете избежать взаимоблокировки курсора, потянув исходные данные для курсора в таблицу Temp и запустив курсор на нем. Это удерживает курсор от блокировки таблицы фактических данных, и единственные блокировки, которые вы получаете, предназначены для обновлений или вставок, выполняемых внутри курсора, которые удерживаются только на время вставки / обновления, а не на время курсора.
источник