Как заблокировать таблицы в SQL Server 2005 и стоит ли вообще это делать?
-
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-сервер выполнит блокировку соответствующих строк или таблиц.
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
...