Исправление тупиковой ситуации в SQL Server: принудительный порядок соединения или автоматическая повторная попытка? - PullRequest
8 голосов
/ 04 марта 2010

У меня есть хранимая процедура, которая выполняет соединение от TableB до TableA:

 SELECT <--- Nested <--- TableA
             Loop   <--
                      |
                      ---TableB

В то же время в транзакции строки вставляются в TableA, а затем в TableB.

Эта ситуация иногда вызывает взаимные блокировки, поскольку выбор хранимой процедуры захватывает строки из TableB , в то время как вставка добавляет строки в TableA , и тогда каждая хочет, чтобы другая отпустила другая таблица:

INSERT     SELECT
=========  ========
Lock A     Lock B
Insert A   Select B
Want B     Want A
....deadlock...

Логика требует INSERT, чтобы сначала добавить строки к A , а затем к B , в то время как мне лично все равно, в каком порядке SQL Server выполняет соединение - до тех пор, пока он присоединяется.

Общая рекомендация по устранению взаимоблокировок - обеспечить, чтобы все обращались к ресурсам в одинаковом порядке. Но в этом случае оптимизатор SQL Server говорит мне, что противоположный порядок «лучше». я могу форсировать другой порядок соединения и получить запрос с худшей производительностью.

Но я должен?

Должен ли я переопределить оптимизатор, теперь и навсегда, порядком соединения, который я хочу использовать?

Или я должен просто перехватить ошибку Собственная ошибка 1205 и повторить оператор выбора?

Вопрос не в том, насколько хуже может выполняться запрос, когда я переопределяю оптимизатор, чтобы он делал что-то неоптимальное. Вопрос в следующем: лучше ли автоматически повторять попытки, чем выполнять худшие запросы?

Ответы [ 3 ]

9 голосов
/ 05 марта 2010

Лучше ли автоматически повторять тупики.Причина в том, что вы можете исправить этот тупик, только чтобы позже пробить еще один.Поведение может меняться между выпусками SQL, если изменяется размер таблиц, меняются спецификации оборудования сервера и даже если изменяется нагрузка на сервер.Если взаимоблокировка является частой, вы должны предпринять активные шаги для ее устранения (индекс обычно является ответом), но для редких взаимоблокировок (скажем, каждые 10 минут или около того) повторная попытка в приложении может замаскировать тупик.Вы можете повторить чтение или записи, поскольку записи, конечно, окружены правильной транзакцией начала транзакции / фиксации, чтобы все операции записи были атомарными и, следовательно, могли повторять их без проблем.

Еще один способ рассмотрения - включение чтение совершенного снимка .Когда эта опция включена, SELECT просто не будет принимать никаких блокировок, но будет давать согласованные чтения.

5 голосов
/ 05 марта 2010

Чтобы избежать взаимных блокировок, одна из наиболее распространенных рекомендаций - «получить блокировки в том же порядке» или «получить доступ к объектам в том же порядке». Очевидно, это имеет смысл, но всегда ли это возможно? Это всегда возможно? Я продолжаю сталкиваться со случаями, когда я не могу следовать этому совету.

Если я храню объект в одной родительской таблице и в одной или нескольких дочерних, я вообще не могу следовать этому совету. При вставке мне нужно сначала вставить родительскую строку. При удалении я должен сделать это в обратном порядке.

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

Таким образом, во многих случаях попытка получить блокировки в одном и том же порядке не предотвращает все тупики. Итак, нам все равно нужна какая-то обработка тупиков - мы не можем предполагать, что сможем устранить их все. Если, конечно, мы не сериализуем весь доступ с помощью Service Broker или sp_getapplock.

Когда мы повторяем попытки после взаимоблокировок, мы, скорее всего, перезапишем изменения других процессов. Нам нужно знать, что очень вероятно, что кто-то еще изменил данные, которые мы намеревались изменить. Особенно, если все читатели работают в режиме изоляции моментальных снимков, тогда читатели не могут быть вовлечены в взаимоблокировки, что означает, что все стороны, вовлеченные в тупик, являются авторами, которые модифицировали или пытались изменить одни и те же данные. Если мы просто перехватываем исключение и автоматически повторяем попытку, мы можем перезаписать чужие изменения.

Это называется потерянными обновлениями, и обычно это неправильно. Как правило, правильное решение после тупика - повторить попытку на более высоком уровне - повторно выбрать данные и решить, сохранять ли таким же образом, как было принято первоначальное решение о сохранении.

Например, если пользователь нажал кнопку «Сохранить» и транзакция сохранения была выбрана в качестве жертвы взаимоблокировки, возможно, было бы неплохо повторно отобразить данные на экране после тупика.

2 голосов
/ 05 марта 2010

Перехват и повторный запуск могут работать, но вы уверены, что SELECT всегда является жертвой тупика?Если вставка является жертвой тупиковой ситуации, вам придется быть более осторожным при повторной попытке.

Я думаю, что самое простое решение в этом случае - NOLOCK или READUNCOMMITTED (то же самое) по вашему выбору.У людей есть оправданные опасения по поводу грязного чтения, но мы годами запускаем NOLOCK повсеместно для обеспечения более высокого уровня параллелизма, и у нас никогда не было проблем.

Я бы также немного больше изучил семантику блокировки.Например, я считаю, что если вы установите уровень изоляции транзакции для моментального снимка (требуется 2005 или более поздняя версия), ваши проблемы исчезнут.

...