Подсказки блокировки - это только подсказки. Вы не можете «заставить» SQL принять определенный тип блокировки.
Вы можете увидеть, какие блокировки были сняты, с помощью следующего запроса:
select tl.request_session_id,
tl.resource_type,
tl.request_mode,
tl.resource_description,
tl.request_status
from sys.dm_tran_locks tl
join sys.partitions pt on pt.hobt_id = tl.resource_associated_entity_id
join sys.objects ob on ob.object_id = pt.object_id
where tl.resource_database_id = db_id()
order by tl.request_session_id
Хорошо, давайте запустим код в окне запроса SSMS:
create table t(i int, j int);
insert t values (1, 1), (2, 2);
begin tran;
update t with(rowlock) set j = 2 where i = 1;
Откройте второе окно SSMS и запустите это:
begin tran;
update t with(rowlock) set j = 2 where i = 2;
Второе выполнение будет заблокировано. Почему?
Запустите запрос блокировки в третьем окне и обратите внимание, что есть две строки с resource_type
из RID
, одна с status
из "grant", другая с status
из "ждать". Мы дойдем до бита RID
через секунду. Также посмотрите на столбец resource_description
для этих строк. Это то же значение.
Хорошо, а что за resource_description
? Это зависит от resource_type
. Но для нашего RID
он представляет собой: идентификатор файла, затем идентификатор страницы, затем идентификатор строки (также известный как слот). Но почему оба исполнения блокируют слот строки 0? Разве они не должны пытаться заблокировать разные строки? В конце концов, мы обновляем разные строки.
Дэвид Браун дал ответ: чтобы найти правильную строку для обновления, SQL должен просканировать всю таблицу, потому что нет индекса, указывающего, как много строк там где i = 1
. При сканировании каждой строки потребуется блокировка обновления. Почему требуется блокировка обновления для каждой строки? Ну это не для того, чтобы "делать" обновление, так сказать. Для этого потребуется эксклюзивная блокировка. Блокировки обновлений практически всегда используются для предотвращения взаимоблокировок.
Итак, первый запрос просканировал строки, взяв блокировку U
для каждой строки. Конечно, он нашел строку, которую хотел обновить сразу, в слоте 0, и взял блокировку X
. И у него все еще есть эта X
блокировка, потому что мы не зафиксировали.
Затем мы запустили второй запрос, который также должен сканировать все строки, чтобы найти ту, которую он хочет. Он начался с попытки взять блокировку U
в первой строке и был заблокирован. Блокировка X
нашего первого запроса блокирует его.
Итак, видите ли, даже при блокировке строк ваш второй запрос все еще заблокирован.
Хорошо, давайте откатим запросы и посмотрим, что произойдет, если первый запрос обновит вторую строку, а второй запрос обновит первую строку? Это работает? Нет! Потому что SQL по-прежнему не может узнать, сколько строк соответствует предикату. Таким образом, первый запрос принимает блокировку обновления в слоте 0, видит, что его не нужно обновлять, берет блокировку обновления в слоте 1, видит правильное значение для i
, берет его эксклюзивную блокировку и ждет, пока мы commit.
Запрос 2 приходит, берет блокировку обновления в слоте 0, видит желаемое значение, берет свою эксклюзивную блокировку, обновляет значение, , а затем пытается принять блокировку обновления в слоте 1 , потому что это также может иметь желаемое значение.
Вы также увидите «блокировку намерения» на следующем «уровне» вверх, то есть на странице. Операция сообщает остальной части механизма, что в какой-то момент в будущем он может захотеть увеличить блокировку до уровня страницы. Но здесь дело не в этом. Блокировка страницы не вызывает проблемы.
Решение в этом случае? Добавьте индекс в столбец i
. В данном случае это, вероятно, первичный ключ. Затем вы можете выполнять обновления в любом порядке. Запрос на блокировку строк в этом случае не имеет значения, потому что SQL не знает, сколько строк соответствует предикату. Но даже если вы попытаетесь принудительно установить блокировку строки в некоторой ситуации, и даже с первичным ключом или соответствующим индексом, SQL все равно может выбрать эскалацию типа блокировки, потому что это может быть более эффективным для блокировать всю страницу или всю таблицу, чем блокировать и разблокировать отдельные строки.