Как заблокировать таблицы в SQL Server 2005 и стоит ли вообще это делать?

StackOverflow https://stackoverflow.com/questions/57625

  •  09-06-2019
  •  | 
  •  

Вопрос

Это потребует некоторых объяснений.Я создал специальную очередь сообщений в SQL Server 2005.У меня есть таблица с сообщениями, содержащими временные метки как для подтверждения, так и для завершения.Хранимая процедура, которую вызывающая сторона выполняет для получения следующего сообщения в своей очереди, также подтверждает получение сообщения.Все идет нормально.Что ж, если система обрабатывает огромное количество транзакций (тысячи в минуту), разве не возможно, чтобы сообщение было подтверждено другим выполнением хранимой процедуры, в то время как другое готово к этому?Позвольте мне помочь, показав мой код SQL в хранимой процедуре:

--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

--Select the entire message
...
...

Не может ли в приведенном выше коде другая хранимая процедура, выполняющаяся в то же время, получить тот же идентификатор и попытаться подтвердить его в то же время?Могу ли я (или должен ли я) реализовать какую-то блокировку, чтобы другой хранимый процесс не подтверждал сообщения, которые запрашивает другой хранимый процесс?

Ух ты, а имело ли что-нибудь из этого вообще смысл?Это немного сложно выразить словами...

Это было полезно?

Решение

Что-то вроде этого

--Grab the next message id
begin tran
declare @MessageId uniqueidentifier
select top 1 @MessageId =   ActionMessageId from UnacknowledgedDemands with(holdlock, updlock);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

-- some error checking
commit tran

--Select the entire message
...
...

Другие советы

Это похоже на ситуацию, когда OUTPUT может быть полезно:

-- Acknowledge and grab the next message
declare @message table (
    -- ...your `ActionMessages` columns here...
)
update ActionMessages
set    AcknowledgedTime = getdate()
output INSERTED.* into @message
where  ActionMessageId in (select top(1) ActionMessageId from UnacknowledgedDemands)
  and  AcknowledgedTime is null

-- Use the data in @message, which will have zero or one rows assuming
-- `ActionMessageId` uniquely identifies a row (strongly implied in your question)
...
...

Там мы обновляем и извлекаем строку в той же операции, которая сообщает оптимизатору запросов точно что мы делаем, позволяя ему выбирать максимально детализированную блокировку и поддерживать ее в течение как можно более короткого времени.(Хотя префикс столбца INSERTED, OUTPUT подобны триггерам, выраженным в терминах UPDATE это похоже на удаление строки и вставку новой.)

Мне нужно больше информации о вашем ActionMessages и UnacknowledgedDemands таблицы (представления/TVF/что угодно), не говоря уже о более глубоком знании автоматической блокировки SQL Server, чтобы сказать, действительно ли это and AcknowledgedTime is null оговорка необходима.Он предназначен для защиты от состояния гонки между подвыбором и обновлением.Я уверен, что в этом не было бы необходимости, если бы мы выбирали из ActionMessages себя (например, where AcknowledgedTime is null с top на update, вместо подвыбора на UnacknowledgedDemands).Я ожидаю, что даже если в этом нет необходимости, это безвредно.

Обратите внимание, что OUTPUT находится в SQL Server 2005 и более поздних версиях.Вы сказали, что используете именно это, но если бы требовалась совместимость с устаревшими установками SQL Server 2000, вы бы пошли другим путем.

@Килхоффер:

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

Редактировать:Кроме того, SQL не обязательно блокирует всю таблицу — он может просто заблокировать необходимые строки.Видеть здесь для обзора блокировки на SQL-сервере.

Вместо явной блокировки, которая SQL Server часто повышает степень детализации, чем хотелось бы, почему бы просто не попробовать этот подход:

declare @MessageId uniqueidentifier
select top 1 @MessageId = ActionMessageId from UnacknowledgedDemands

update ActionMessages
  set AcknowledgedTime = getdate()
  where ActionMessageId = @MessageId and AcknowledgedTime is null

if @@rowcount > 0
  /* acknoweldge succeeded */
else
  /* concurrent query acknowledged message before us,
     go back and try another one */

Чем меньше вы блокируете, тем выше у вас параллелизм.

Стоит ли вам обрабатывать вещи одно за другим?Разве вам не следует просто заставить SQL Server подтверждать все неподтвержденные сообщения с сегодняшней датой и возвращать их?(все также в транзакции, конечно)

Узнайте больше о блокировке выбора SQL Server. здесь и здесь.SQL Server имеет возможность вызывать блокировку таблицы при выборе.Во время транзакции с таблицей ничего не произойдет.Когда транзакция завершится, любые вставки или обновления будут разрешены сами собой.

Вы хотите обернуть свой код в транзакцию, тогда SQL-сервер выполнит блокировку соответствующих строк или таблиц.

begin transaction

--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

commit transaction

--Select the entire message
...
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top