Как заблокировать таблицы в SQL Server 2005, и должен ли я это сделать?
Об этом будет сказано. То, что я сделал, - создать специальную очередь сообщений в SQL Server 2005. У меня есть таблица с сообщениями, которые содержат отметки времени для подтверждения и завершения. Хранимая процедура, которую выполняют вызывающие абоненты для получения следующего сообщения в очереди, также подтверждает сообщение. Все идет нормально. Ну, если система испытывает огромное количество транзакций (в тысячах в минуту), не возможно ли, чтобы сообщение подтверждалось другим выполнением хранимой процедуры, а другая была подготовлена к тому самому? Позвольте мне помочь, показывая мой код SQL в сохраненном proc:
--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
...
...
В приведенном выше коде не удалось запустить другую хранимую процедуру одновременно с тем же идентификатором и попытаться подтвердить его одновременно? Мог ли я (или должен ли я) реализовать какую-то блокировку, чтобы предотвратить попытку другого сохраненного proc от подтверждения сообщений, которые запрашивает другой сохраненный proc?
Ничего себе, это вообще не имело смысла? Это немного сложно сказать...
Ответы
Ответ 1
Что-то вроде этого
--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
...
...
Ответ 2
Это похоже на ситуацию, когда 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
(views/TVFs/whatever), не говоря уже о более широком знании автоматической блокировки SQL Server, чтобы сказать, нужно ли это предложение and AcknowledgedTime is null
. Он должен защищаться от состояния гонки между суб-выбором и обновлением. Я уверен, что это было бы необязательно, если бы мы сами выбирали из ActionMessages
(например, where AcknowledgedTime is null
с top
на UPDATE
, а не подвыбор на UnacknowledgedDemands
). Я ожидаю, что даже если это не нужно, это безвредно.
Обратите внимание, что OUTPUT
находится в SQL Server 2005 и выше. Это то, что вы сказали, что используете, но если требуется совместимость с гериатрическими установками SQL Server 2000, вам нужно пойти другим путем.
Ответ 3
@Kilhoffer:
Весь пакет SQL анализируется перед выполнением, поэтому SQL знает, что вы собираетесь делать обновление в таблице, а также выбирать из него.
Изменить: Кроме того, SQL не обязательно будет блокировать всю таблицу - он может просто заблокировать необходимые строки. См. здесь для обзора блокировки на SQL-сервере.
Ответ 4
Вместо явной блокировки, которая часто повышается с помощью 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 */
Чем меньше вы заблокируете - тем выше concurrency.
Ответ 5
Должны ли вы действительно обрабатывать вещи один за другим? Разве вам не нужно, чтобы SQL Server признавал все непризнанные сообщения с сегодняшней датой и возвращал их? (все также в транзакции, конечно)
Ответ 6
Подробнее о SQL Server Выберите Locking здесь и здесь. SQL Server имеет возможность вызывать блокировку таблицы при выборе. Во время транзакции ничего не произойдет с таблицей. Когда транзакция завершится, любые вставки или обновления будут разрешаться самостоятельно.
Ответ 7
Вы хотите обернуть свой код в транзакцию, тогда 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
...