21. Взаимоблокировки, их обнаружение и устранение - KattyOG/Database GitHub Wiki
Взаимоблокировки или тупиковые ситуации (deadlocks) возникают тогда, когда одна из транзакций не может
завершить свои действия, поскольку вторая транзакция заблокировала нужные ей ресурсы, а вторая в то же время
ожидает освобождения ресурсов первой транзакцией. На рисунке транзакция Т1 зависит от транзакции Т2 для ресурса
блокировки таблицы Детали. Аналогично транзакция Т2 зависит от транзакции Т1 для ресурса блокировки таблицы
Поставщик. Так как эти зависимости из одного цикла, возникает взаимоблокировка транзакций T1 и T2.
Транзакция Т1 не может завершиться до того, как завершится транзакция Т2, а транзакция Т2 заблокирована транзакцией
Т1. Такое условие также называется цикличной зависимостью: транзакция Т1 зависит от транзакции Т2, а транзакция Т2 зависит от транзакции Т1 и этим замыкает цикл. Обе транзакции находятся в состоянии взаимоблокировки и будут всегда находиться в состоянии ожидания, если взаимоблокировка не будет разрушена внешним процессом.
Обнаружение взаимоблокировки выполняется потоком диспетчера блокировок, который периодически производит поиск по всем задачам в экземпляре компонента Database Engine. Следующие пункты описывают процесс поиска:
- Значение интервала поиска по умолчанию составляет 5 секунд.
- Если диспетчер блокировок находит взаимоблокировки, интервал обнаружения взаимоблокировок снижается с 5 секунд до 100 ми ллисекунд в зависимости от частоты взаимоблокировок.
- Если поток диспетчера блокировки прекращает поиск взаимоблокировок, компонент Database Engine увеличивает интервал до 5 секунд.
- Если взаимоблокировка была только что найдена, предполагается, что следующие потоки, которые должны ожидать блокировки, входят в цикл взаимоблокировки. Первая пара элементов, ожидающих блокировки, после того как взаимоблокировка была обнаружена, запускает поиск взаимоблокировок вместо того, чтобы ожидать следующий интервал обнаружения взаимоблокировки. Например, если текущее значение интервала равно 5 секунд и была обнаружена взаимоблокировка, следующий ожидающий блокировки элемент немедленно приводит в действие детектор взаимоблокировок. Если этот ожидающий блокировки элемент является частью взаимоблокировки, она будет обнаружена немедленно, а не во время следующего поиска взаимоблокировок.
- Компонент Database Engine обычно выполняет только периодическое обнаружение взаимоблокировок. Так как число взаимоблокировок, произошедших в системе, обычно мало, периодическое обнаружение взаимоблокировок помогает сократить издержки от взаимоблокировок в системе.
- Если монитор блокировок запускает поиск взаимоблокировок для определенного потока, он идентифицирует ресурс, ожидаемый потоком. После этого монитор блокировок находит владельцев определенного ресурса и рекурсивно продолжает поиск взаимоблокировок для этих потоков до тех пор, пока не найдет цикл. Цикл, определенный таким способом, формирует взаимоблокировку.
- После обнаружения взаимоблокировки компонент Database Engine завершает взаимоблокировку, выбрав один из
потоков в качестве жертвы взаимоблокировки. Компонент Database Engine прерывает выполняемый в данный момент
пакет потока, производит откат транзакции жертвы взаимоблокировки и возвращает приложению ошибку 1205. Откат
транзакции жертвы взаимоблокировки снимает все блокировки, удерживаемые транзакцией. Это позволяет
транзакциям потоков разблокироваться, и продолжить выполнение. Ошибка 1205 жертвы взаимоблокировки
записывает в журнал ош ибок сведения обо всех потоках и ресурсах, затронутых взаимоблокировкой.
Каждые пять секунд SQL Server проверяет состояние текущих транзакций на предмет наличия блокировок, которые ожидают своей очереди. В случае наличия подобных блокировок, SQL Server берет их на заметку. Через следующие пять секунд производится повторная проверка всех открытых блокировок, и если одна из отмеченных блокировок по прежнему находится в состоянии ожидания, выполняется рекурсивная проверка всех открытых транзакций на предмет существования в очереди замкнутых циклов. Если такой цикл обнаружен, в нем выбираются одна или несколько «жертв» взаимоблокировки.
По умолчанию в качестве жертвы взаимоблокировки выбирается сеанс, выполняющий ту транзакцию, откат которой потребует меньше всего затрат. В качестве альтернативы пользователь может указать приоритет сеансов, используя инструкцию SET DEADLOCK_PRIORITY. DEADLOCK_PRIORITY может принимать значения LOW, NORMAL или HIGH или в качестве альтернативы может принять любое целочисленное значение на отрезке [-10..10]. По умолчанию DEADLOCK_PRIORITY устанавливается на значение NORMAL. Если у двух сеансов имеются различные приоритеты, то в качестве жертвы взаимоблокировки будет выбран сеанс с более низким приоритетом. Если у обоих сеансов установлен одинаковый приоритет, то в качестве жертвы взаимоблокировки будет выбран сеанс, откат которого потребует наименьших затрат. Если сеансы, вовлеченные в цикл взаимоблокировки, имеют один и тот же приоритет и одинаковую стоимость, то жертва взаимоблокировки выбирается случайным образом.
Невозможно полностью исключить возникновение взаимоблокировок в сложных программных системах, но с практической точки зрения можно сделать их вероятность настолько малой, что данный вопрос перестанет быть актуальным. Для того чтобы избежать появления взаимоблокировок или, по крайней мере, свести их количество к минимуму, надо следовать простым правилам:
- обращайтесь к объектам в одном и том же порядке;
- делайте транзакции как можно более короткими и размещайте их в одном пакете;
- используйте наименьший возможный уровень изоляции транзакций;
- не допуск айте разрывов внутри транзакции (со стороны пользователя либо в результате разделения пакета);
- в контролируемой среде используйте связанные подключения.