Почему блокировка на уровне строк не работает правильно на сервере SQL? - PullRequest
5 голосов
/ 25 февраля 2010

Это продолжение от Когда я обновляю / вставляю одну строку, должна ли она блокировать всю таблицу?

Вот моя проблема.

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

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

Однако происходит то, что, хотя я использую блокировку строки для блокировки записи, он блокирует запрос к другому процессу, чтобы изменить полностью несвязанную строку в той же таблице, которая также указала бы updlock подсказка вместе с rowlock .

Вы можете воссоздать это, сделав стол

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Locks](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [LockName] [varchar](50) NOT NULL,
    [Locked] [bit] NOT NULL,
 CONSTRAINT [PK_Locks] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 100) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Locks] ADD  CONSTRAINT [DF_Locks_LockName]  DEFAULT ('') FOR [LockName]
GO
ALTER TABLE [dbo].[Locks] ADD  CONSTRAINT [DF_Locks_Locked]  DEFAULT ((0)) FOR [Locked]
GO

Добавьте две строки для блокировки с LockName = ‘A’ и одну для LockName = ‘B’

Затем создайте два запроса для одновременного выполнения транзакции:

Запрос 1:

Commit
Begin transaction
select * From Locks with (updlock rowlock) where LockName='A'

Запрос 2:

select * From Locks with (updlock rowlock) where LockName='B'

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

Когда вы запускаете Запрос 1 блокировки являются проблемами для строки, и любые последующие запросы для LockName = ’A’ должны будут ждать. Это правильное поведение.

Это немного расстраивает, когда вы запускаете Query 2 , и вы блокируетесь до тех пор, пока Query 1 не завершит работу, даже если это не связанные записи. Если вы затем снова выполните Query 1 , как я это сделал выше, он совершит предыдущую транзакцию, Query 2 будет запущен, а затем Query 1 будет снова заблокировать запись.

Пожалуйста, предложите несколько советов о том, как я мог бы сделать так, чтобы он правильно блокировал ТОЛЬКО одну строку и не препятствовал обновлению других элементов.

PS. Holdlock также не может произвести правильное поведение после обновления одной из строк.

Ответы [ 4 ]

10 голосов
/ 25 февраля 2010

В SQL Server подсказки блокировки применяются к проверенным объектам, а не к ним.

Обычно движок устанавливает общую блокировку на объекты (страницы и т. Д.) Во время чтения и снимает их (или не поднимает в SERIALIZABLE транзакциях) после завершения сканирования.

Однако вы даете указание двигателю установить (и поднять) блокировку обновлений, которые несовместимы друг с другом.

Транзакция B блокируется при попытке поместить UPDLOCK в строку, уже заблокированную с UPDLOCK транзакцией A.

Если вы создадите индекс и принудительно будете его использовать (чтобы никогда не происходило конфликтное чтение), ваши таблицы не будут блокироваться:

CREATE INDEX ix_locks_lockname ON locks (lockname)

Begin transaction
select * From Locks with (updlock rowlock INDEX (ix_locks_lockname)) where LockName='A'

Begin transaction
select * From Locks with (updlock rowlock INDEX (ix_locks_lockname)) where LockName='B'
1 голос
/ 25 февраля 2010

Если вы хотите поставить в очередь в SQL Server, используйте подсказки UPDLOCK, ROWLOCK, READPAST . Это работает.

Я бы хотел изменить ваш подход, а не пытаться изменить поведение SQL Server ...

1 голос
/ 25 февраля 2010

Я не уверен, что вы пытаетесь достичь, но обычно те, кто сталкиваются с подобными проблемами, хотят использовать sp_getapplock. Рассказывает Тони Роджерсон: Поддержка параллелизма путем создания собственных замков (мьютексы в SQL)

1 голос
/ 25 февраля 2010

Для запроса 2 попробуйте использовать подсказку READPAST - это (цитата):

Указывает, что компонент Database Engine не читать строки, заблокированные другими сделки. Под большинством обстоятельства, то же самое относится и к страницы. Когда указано READPAST, блокировки как на уровне строк, так и на уровне страниц пропущены То есть база данных Двигатель пропускает мимо строк или страниц вместо блокировки тока транзакция до блокировки выпущен

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

Редактировать 1:
Это может быть вызвано, если у вас нет индекса в поле LockName. С помощью индекса запрос 2 может выполнить поиск индекса по точной строке. Но без него будет выполняться сканирование (проверка каждой строки), что означает, что он задерживается первой транзакцией. Так что, если он не проиндексирован, попробуйте его проиндексировать.

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