Как заблокировать таблицы в 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
...