Блокировки в MS SQL Server 2000
Информация - Компьютеры, программирование
Другие материалы по предмету Компьютеры, программирование
?риоритета. Это значение задается на уровне соединения следующей командой:
SET DEADLOCK_PRIORITY { LOW | NORMAL | @deadlock_var }Задание меньшего приоритета для соединения приводит к откату транзакций при возникновении взаимоблокировок с кодом ошибки 1205.
Давайте рассмотрим простой пример взаимоблокировки. Для большей наглядности я продемонстрирую его на одной таблице из трех (!) строк.
Итак, мы продолжаем работать с нашей таблицей test, на поле i которой создан некластерный индекс.
В первой сессии выполним такой запрос:
SET IMPLICIT_TRANSACTIONS on
select * from test with(updlock) where i = 2Здесь мы наложили блокировку обновления на вторую строку таблицы. Теперь во второй сессии введем следующий код:
SET IMPLICIT_TRANSACTIONS on
select * from test with (updlock) where i = 1Та же блокировка обновления накладывается на первую строчку. Перейдем снова к первой сессии:
--SET IMPLICIT_TRANSACTIONS on
--select * from test with(updlock) where i = 2
update test set n = (select n from test where i = 2) where i = 1Так как мы пытаемся получить на первую строку монопольную блокировку, которая несовместима с текущей блокировкой обновления, сессия блокируется.
Теперь во второй сессии введем похожую команду:
--SET IMPLICIT_TRANSACTIONS on
--select * from test with (updlock) where i = 1
update test set n = (select n from test where i = 1) where i = 2Вот она, взаимоблокировка! Обе сессии заблокированы. Однако наше ликование будет недолгим: примерно через 5 секунд менеджер блокировок сообразит, что возникла взаимоблокировка и откатит первую транзакцию.
Server: Msg 1205, Level 13, State 50, Line 3
Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen
as the deadlock victim. Rerun the transaction.Конечно, пример несколько надуманный, зато он хорошо иллюстрирует проблему: на пустом месте можно получить взаимоблокировку даже на табличке из двух строк.
При написании хранимых процедур, которые и являются основным источником взаимных блокировок, необходимо руководствоваться следующим простым правилом: если две и более транзакций используют совместные ресурсы, порядок наложения однотипных блокировок на эти ресурсы должен быть одинаковым.
Это правило помогает, но не во всех случаях. Рассмотрим такой пример. В первой сессии выполняем команду:
set implicit_transactions on
select * from test with (repeatableread)Это приводит к наложению разделяемых блокировок на все строки таблицы test. Во второй сессии выполняем тот же запрос:
set implicit_transactions on
select * from test with (repeatableread)Так как разделяемая блокировка совместима сама с собой, запрос выполняется нормально.
Блокировки для первой строки приведены в таблице 17.
spiddbidObjIdIndIdTypeResourceModeStatus55819930581360RID1:31:00SGRANT57819930581360RID1:31:00SGRANTНас интересует именно первая строка, так как сейчас мы попытаемся в первой сессии изменить данные:
--set implicit_transactions on
--select * from test with (repeatableread)
update test set n = other where i = 1Сессия блокируется, так как монопольная блокировка, необходимая для выполнения изменения, не совместима с разделяемой. Теперь блокировки выглядят так, как показано в таблице 18.
spiddbidObjIdIndIdTypeResourceModeStatus55819930581360RID1:31:00SGRANT57819930581360RID1:31:00UGRANT57819930581360RID1:31:00XCNVTОбнаружив нужную строчку, сервер пытается преобразовать текущую разделяемую блокировку в блокировку обновления, что ему удается. Это делается для того, чтобы после нахождения нужного ресурса в процессе подготовительных операций перед обновлением никакая другая транзакция не смогла получить блокировку на изменение (блокировку обновления или монопольную блокировку). Затем, непосредственно перед обновлением, сервер пытается преобразовать текущую блокировку обновления в монопольную, что ему, естественно, не удается.
Если теперь выполнить во второй сессии ту же команду:
--set implicit_transactions on
--select * from test with (repeatableread)
update test set n = other where i = 1мы получим мертвую блокировку. Причем во второй сессии транзакция даже не сможет преобразовать разделяемую блокировку в блокировку обновления, так как блокировка обновления не совместима сама с собой.
Чтобы избавиться от подобных ситуаций, нужно устанавливать при запросе данных вместо разделяемой блокировки сразу блокировку обновления. В этом случае вторая сессия будет заблокирована на команде выборки данных до завершения транзакции в первой сессии. Однако и при установке блокировки обновления нельзя быть уверенным, что не произойдет взаимного блокирования транзакций. Если во второй сессии просто выполнить запрос с уровнем изоляции REPEATABLE READ, на все строки опять будет установлена разделяемая блокировка, и при попытке обновления записи в первой сессии, транзакция будет заблокирована. Единственным выходом из ситуации, когда происходит выполнение транзакций с разными уровнями изоляции, будет наложение монопольных блокировок на все строки при запросе данных в первой сессии. Хотя такой метод строго не рекомендуется, он возможен, благодаря наличию хинта xlock. Если вносимые транзакцией изменения будут затрагивать более 70% таблицы, есть смысл вместо xlock установить хинт tablockx, хотя менеджер блокировок достаточно умен, чтобы самостоятельно выполнить эскалацию блокировок.
Заключение
В заключение я хочу рассмотреть вопросы, которые не были затронуты в данной статье, но могут иметь определенный интерес.
Владельцы блокировки
Возможно, вы не задумывались над этим вопросом, или думали, что владельцем всех блокировок является менеджер блокировок, однако это не так. У каждой блокировки есть владелец, и его можно найти в таблице syslockinfo в поле req_ownertype. Это поле может принимать три значения: 1, 2 и 3. Если значение равно 1, владельцем блокировки является транзакция это самая распространенная ситуация. Если req_ownertype равен 2 вл?/p>