Очевидно, что третий вариант в подавляющем большинстве случаев предпочтительнее с точки зрения производительности.
Распределенная взаимоблокировка
Взаимоблокировка считается распределенной, если часть графа ожидания находится вне сервера. Можно выделить два типа.
1. Часть графа ожидания находится в клиентском приложении. Предположим, что группе клиентских потоков необходимо синхронизировать доступ к какому-либо ресурсу помимо СУБД. Один поток может захватить клиентский объект и ожидать снятия блокировки в БД. В это время другой поток, захвативший объект БД, необходимый первому потоку, может ожидать, пока первый поток освободит клиентский объект. Главная неприятность подобной взаимоблокировки в том, что она в принципе не детектируется, и приложение повисает намертво, если время ожидания блокировки не выставлено в разумных пределах. Можно порекомендовать следующие методы борьбы:
Использовать одно подключение к базе для всех потоков. В этом случае потоки на сервере не будут блокировать друг друга.
Microsoft SQL Server поддерживает механизм «связанных подключений» (BoundConnections), когда несколько подключений на клиенте «связываются» вместе и воспринимаются сервером как одно подключение. Эффект тот же самый, что и в предыдущем случае – потоки не блокируют друг друга при доступе к объектам СУБД.
Для синхронизации доступа к клиентскому объекту использовать механизм блокировок сервера. Некоторые серверы, в том числе и Microsoft SQL Server, имеют возможность предоставить свой менеджер блокировок для нужд внешних приложений. В этом случае весь граф ожидания находится на сервере, и определить взаимоблокировку не составляет никакого труда.
2. Часть графа ожидания находится на другом сервере баз данных. Тут, как ни странно, все еще сложнее. Для начала можно вспомнить, что не все серверы используют блокировки для синхронизации доступа. Тем не менее, даже если все участники транзакции – серверы, использующие блокировки, но разных производителей, все равно не обойтись без стандартного представления графа ожидания, который понимали бы все СУБД. Такового на данный момент не существует. И, наконец, даже если речь идёт только об одном сервере, то объём информации о графах ожидания, который нужно передавать по сети, может быть довольно значительным, хотя теоретически, в этом случае обнаружить взаимоблокировку можно. Можно было бы также построить механизм борьбы с распределенными взаимоблокировками на основе временных меток. Тогда объем информации, необходимый для предотвращения взаимоблокировки, был бы значительно меньше, но в случае большого числа откатов этот способ малоприменим. На данный момент Microsoft SQL Server не поддерживает определение распределенных взаимоблокировок между различными серверами.
Вообще говоря, при некоторой ловкости рук мертвые блокировки возможны практически всегда. :) Существует множество способов добиться описанного эффекта, сделать это совсем не сложно и для тех, кто подобными вещами никогда не развлекался, написание различных сценариев, приводящих к взаимоблокировкам, могло бы быть весьма полезным упражнением, помогающим понять принципы работы менеджера блокировок и другие особенности внутренней механики сервера.
Лучший же способ избежать мертвых блокировок – это четко представлять себе, что делает сервер при тех или иных операциях, и не допускать стандартных и хорошо изученных ошибок.
В первую очередь необходимо придерживаться единого порядка доступа ко всем ресурсам. Причём имеется в виду и то, что транзакция не должна возвращаться к уже заблокированному ей же ресурсу для наложения более сильной блокировки. Другими словами, мы сразу накладываем самую сильную из нужных нам блокировок. При строгом соблюдении данного правила мертвые блокировки вообще невозможны. Другое дело, что оно накладывает достаточно сильные ограничения, и в большинстве случаев так не поступают, например блокировки обновления – вполне разумный компромисс.
Очень часто в различных performance tips рекомендуют везде, где только можно, устанавливать уровень изоляции в READ UNCOMMITED. Я бы хотел предостеречь от этого шага. Вероятность мертвой блокировки при использовании этого уровня изоляции, конечно, понизится, но риск привести базу в несогласованное состояние при этом возрастает многократно. Бороться с последствиями этого эффекта гораздо сложнее, чем с последствиями возникновения взаимоблокировки. В подавляющем большинстве случаев можно найти выход из ситуации, не используя этот уровень изоляции. Как правило, необходимость его использования является следствием ошибок проектирования.
Довольно часто рекомендуется использовать наиболее низкий уровень изоляции. По сути своей совет правильный, так как при этом уменьшается число ожидающих на блокировках транзакций, уменьшается время удерживания блокировок и, как следствие, уменьшается вероятность того, что различные транзакции схлестнутся в борьбе за ресурсы. Но, как было показано в предыдущем примере, возможны и исключения. Там взаимоблокировка произойдет при уровнях изоляции READ COMMITED и REPEATABLE READ, а при уровне изоляции SERIALIZABLE ее уже не случится. То же самое относится и к рекомендации блокировать как можно меньший объем. В большинстве случаев это действительно помогает, по тем же самым причинам: потенциально опасные транзакции с меньшей вероятностью захватят общие ресурсы, но опять-таки возможны исключения.
Вообще же тут все зависит от конкретной ситуации. Для поиска причин необходимо анализировать errorlog и данные Profler’а, смотреть, на каких ресурсах произошла взаимоблокировка, какие операции «передрались» между собой. Даже если истинная причина не будет найдена, или из-за какой-либо особенности приложения полностью устранить подобную ситуацию будет невозможно, тщательный анализ поможет найти способ существенно снизить возможность возникновения взаимоблокировки. Исходить нужно из того, что любая взаимоблокировка не является таинственным природным феноменом, а имеет свою причину. Эту причину необходимо найти и попробовать устранить. И только если окажется, что причина непонятна, трудно устранима или взаимоблокировка случается крайне редко, можно рассматривать повторное выполнение отмененной транзакции как окончательный вариант.
Список литературы
[1] Системы Баз Данных, полный курс. Г. Гарсиа-Молино, Дж. Ульман, Дж. Уидом.
[2] Concurrency control and recovery in database systems. Philip A. Bernstein, Vassos Hadzilacos, Nathan Goodman.
[3] Введение в системы баз данных. К. Дж. Дейт
[4] Inside Microsoft SQL Server 2000. Kalen Delaney
[5] Microsoft Books OnLine (Документацияк Microsoft SQL Server)
[6] Форумы на RSDN.RU и группа конференций FIDO su.dbms.*