Нулевой тупик SQL по замыслу - какие-либо шаблоны кодирования? - PullRequest
31 голосов
/ 21 сентября 2008

Я сталкиваюсь с очень редкими, но раздражающими взаимными блокировками SQL в веб-приложении .NET 2.0, работающем поверх MS SQL Server 2005. В прошлом мы имели дело с взаимными блокировками SQL очень эмпирическим способом - в основном, настраивая запросы до это работает.

Тем не менее, я нашел этот подход очень неудовлетворительным: трудоемким и ненадежным. Я бы предпочел следовать детерминированным шаблонам запросов, которые гарантировали бы при разработке , что с SQL не будет возникать тупиковых ситуаций - когда-либо.

Например, в многопоточном программировании на C # простое правило проектирования, такое как , блокировки должны быть приняты в соответствии с их лексикографическим порядком , гарантирующим, что без блокировки никогда не будет.

Существуют ли какие-либо шаблоны кодирования SQL, гарантирующие защиту от тупиков?

Ответы [ 10 ]

21 голосов
/ 22 сентября 2008

Написание кода, защищающего от тупиков, действительно сложно. Даже если вы обращаетесь к таблицам в том же порядке, вы все равно можете получить взаимоблокировки [1]. Я написал пост в своем блоге , в котором подробно рассматриваются некоторые подходы, которые помогут вам избежать и разрешить тупиковые ситуации.

Если вы хотите, чтобы два оператора / транзакции никогда не зашли в тупик, вы можете достичь этого, наблюдая, какие блокировки потребляет каждый оператор, используя системную хранимую процедуру sp_lock . Чтобы сделать это, вы должны быть очень быстрыми или использовать открытую транзакцию с подсказкой удержания.


Примечания:

  1. Любой оператор SELECT, которому требуется более одной блокировки одновременно, может взаимоблокировать с интеллектуально спроектированной транзакцией, которая захватывает блокировки в обратном порядке.
13 голосов
/ 22 сентября 2008

Нулевые взаимоблокировки - это в общем случае невероятно дорогостоящая проблема в общем случае, потому что вы должны знать все таблицы / obj, которые вы собираетесь читать и изменять для каждой запущенной транзакции (включая SELECT). Общая философия называется упорядоченная строгая двухфазная блокировка (не путать с двухфазной фиксацией) (http://en.wikipedia.org/wiki/Two_phase_locking; даже 2PL не гарантирует отсутствие тупиков)

Очень немногие СУБД действительно реализуют строгий 2PL из-за огромного снижения производительности, которое вызывает такая вещь (бесплатных обедов нет), в то время как все ваши транзакции ждут выполнения даже простых операторов SELECT.

В любом случае, если это то, что вас действительно интересует, взгляните на SET ISOLATION LEVEL в SQL Server. Вы можете настроить это по мере необходимости. http://en.wikipedia.org/wiki/Isolation_level

Для получения дополнительной информации см. Википедию по сериализуемости: http://en.wikipedia.org/wiki/Serializability

Тем не менее, отличная аналогия похожа на ревизии исходного кода: регистрируйтесь рано и часто. Сделайте ваши транзакции небольшими (количество операторов SQL, количество строк изменено) и быстрыми (время настенных часов помогает избежать столкновений с другими). Было бы неплохо сделать много вещей за одну транзакцию - и в целом я согласен с этой философией - но если вы испытываете много тупиков, вы можете разбить транс на более мелкие, а затем проверяйте их статус в приложении по мере продвижения. TRAN 1 - OK Да / Нет? Если Д, отправить TRAN 2 - ОК, Д / Н? и т. д.

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

3 голосов
/ 08 февраля 2009

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

Общие ответы, чтобы уменьшить возможности тупиковой ситуации:

  1. Базовая оптимизация запросов (правильное использование индексов), избегая дизайна горячих точек, удерживая транзакции в течение кратчайшего возможного времени ... и т.д.

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

  3. Взаимные блокировки в MSSQL часто возникают из-за модели параллелизма чтения по умолчанию, поэтому очень важно не зависеть от нее - допускается использование стиля Oracle MVCC во всех проектах. Используйте изоляцию моментального снимка или, если возможно, уровень чтения READ UNCOMMITED.

2 голосов
/ 14 ноября 2011

Я считаю, что следующий полезный шаблон чтения / записи является доказательством мертвой блокировки, учитывая некоторые ограничения:

Ограничения:

  1. Один стол
  2. Индекс или PK используется для чтения / записи, поэтому движок не прибегает к блокировке таблиц.
  3. Пакет записей может быть прочитан с использованием одного оператора SQL where.
  4. Использование терминологии SQL Server.

Цикл записи:

  1. Все записи в одной транзакции "Read Committed".
  2. Первое обновление в транзакции относится к определенной, всегда присутствующей записи в каждой группе обновлений.
  3. Несколько записей могут быть записаны в любом порядке. (Они "защищены" по записи к первой записи).

Цикл чтения:

  1. Уровень транзакции чтения по умолчанию
  2. Нет транзакций
  3. Чтение записей как одного оператора выбора.

Преимущества:

  1. Вторичные циклы записи блокируются при записи первой записи, пока первая транзакция записи не завершится полностью.
  2. Чтения блокируются / ставятся в очередь / выполняются атомарно между фиксациями записи.
  3. Достижение согласованности на уровне транзакции без использования «Сериализуемый».

Мне нужно, чтобы это тоже работало, поэтому, пожалуйста, прокомментируйте / исправьте !!

1 голос
/ 03 ноября 2008

То, что никто не упомянул (что удивительно), это то, что в случае SQL-сервера многие проблемы с блокировкой могут быть устранены с помощью правильного набора покрывающих индексов для рабочей нагрузки запросов БД. Зачем? Потому что это может значительно сократить количество просмотров закладок в кластеризованном индексе таблицы (при условии, что это не куча), тем самым уменьшая конкуренцию и блокировку.

1 голос
/ 22 сентября 2008

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

1 голос
/ 22 сентября 2008

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

Изолируйте ваши соединения базы данных с определенным классом в вашем приложении (каждое соединение должно происходить из этого класса) и укажите, что соединения «только для запроса» устанавливают уровень изоляции «грязное чтение» ... эквивалент (nolock) при каждом присоединении.

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

1 голос
/ 21 сентября 2008

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

Еще один крутой трюк - объединить 2 SQL-выражения в одно, когда это возможно. Отдельные операторы всегда являются транзакционными. Например, используйте «UPDATE ... SELECT» или «INSERT ... SELECT», используйте «@@ ERROR» и «@@ ROWCOUNT» вместо «SELECT COUNT» или «IF (EXISTS ...)»

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

0 голосов
/ 22 сентября 2008

Не прямой ответ на ваш вопрос, а пища для размышлений:

http://en.wikipedia.org/wiki/Dining_philosophers_problem

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

0 голосов
/ 22 сентября 2008

Быстрый ответ - нет, нет гарантированной техники.

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

Лучший способ решить проблемы взаимоблокировки SQL, как и большинство проблем с производительностью и доступностью, - это посмотреть на рабочую нагрузку в профилировщике и понять поведение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...