обновление с rowlock на сервере MS SQL - PullRequest
1 голос
/ 11 июля 2020

Я пытался понять ROWLOCK на сервере SQL, чтобы обновить запись после ее блокировки. Вот мое наблюдение и я хотел бы получить подтверждение, похож ли ROWLOCK на блокировку таблицы или страницы, или я не пробовал это правильно. ROWLOCK должен быть блокировкой только для строки, а не для таблицы или страницы.

Вот что я пробовал:

Я создал простую таблицу: row_lock_temp_test с двумя столбцами ID и Имя без PK или индекса. Теперь я открываю SQL сервер, два разных клиента, но с одинаковыми учетными данными, и пробовал выполнить набор следующих запросов:

Клиент 1:

1: BEGIN TRANSACTION;
2: update row_lock_temp_test set name = 'CC' where id = 2
3: COMMIT

Клиент 2:

1: BEGIN TRANSACTION;
2: update row_lock_temp_test set name= 'CC' where id = 2
3: COMMIT

Я выполнил запросы 1, 2 на C -1, перешел к C -2 и выполнил те же запросы, оба клиента выполнили запросы, а затем я зафиксировал транзакцию, все в порядке.

Затем я добавил RowLock для обновления запроса,

C -1

  1: BEGIN TRANSACTION;
  2: update row_lock_temp_test WITH(rowlock) set name = 'CC' where id = 2
  3: COMMIT

C -2

1: BEGIN TRANSACTION;
2: update row_lock_temp_test WITH(rowlock) set name = 'CC' where id = 2
3: COMMIT

Теперь я выполнил запрос 1 и 2 на C -1, а затем перешел к C -2 и попытался выполнить те же 2 запроса, но запрос застрял, как и ожидалось, потому что строка заблокирована C -1, поэтому она должна находиться в очереди до транзакция совершена C -1. как только я совершил транзакцию на C -1, запрос на C -2 был выполнен, а затем я зафиксировал транзакцию на C -2. Все хорошо.

здесь я попробовал другой сценарий для выполнения того же набора запросов с идентификатором строки = 3

C -2

 1: BEGIN TRANSACTION;
 2: update row_lock_temp_test WITH(rowlock) set name = 'CC' where id = 3
 3: COMMIT

Я выполнил первые два запросы в C -1, а затем перешли к выполнению первых двух запросов из C -2, идентификатор строки отличается у обоих клиентов, но все же запрос в C -2 застрял. Это означает, что при обновлении запроса с id = 2 он заблокировал страницу или таблицу, я ожидал блокировки строки, но это похоже на блокировку страницы или таблицы.

Я также пробовал использовать xlock, HOLDLOCK и UPDLOCK с разные комбинации, но он всегда блокирует таблицу. есть ли возможность заблокировать только строку.

Выбор и вставка работают должным образом.

Заранее спасибо.

1 Ответ

1 голос
/ 12 июля 2020

Подсказки блокировки - это только подсказки. Вы не можете «заставить» 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 все равно может выбрать эскалацию типа блокировки, потому что это может быть более эффективным для блокировать всю страницу или всю таблицу, чем блокировать и разблокировать отдельные строки.

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