SQL Server SELECT / UPDATE Странность хранимых процедур - PullRequest
3 голосов
/ 18 февраля 2009

У меня есть таблица, которую я использую в качестве рабочей очереди. По сути, он состоит из первичного ключа, части данных и флага состояния (обработан / необработан). У меня есть несколько процессов, пытающихся захватить следующую необработанную строку, поэтому мне нужно убедиться, что они соблюдают правильную семантику блокировки и обновления, чтобы избежать нежелательности состояния гонки. Для этого я определил хранимую процедуру, которую они могут вызвать:

CREATE PROCEDURE get_from_q
AS
DECLARE @queueid INT;
BEGIN TRANSACTION TRAN1;

SELECT TOP 1 
    @queueid = id 
FROM 
    MSG_Q WITH (updlock, readpast) 
WHERE 
    MSG_Q.status=0;

SELECT TOP 1 * 
FROM
    MSG_Q 
WHERE 
    MSG_Q.id=@queueid;

UPDATE MSG_Q 
SET status=1 
WHERE id=@queueid;

COMMIT TRANSACTION TRAN1;

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

Теперь процедура работает так, как указано выше, и это здорово. Однако, когда я собирал это вместе, я обнаружил, что если второй SELECT и UPDATE поменялись местами по порядку (т.е. сначала UPDATE, а затем SELECT), я вообще не получил никаких данных. И нет, не имело значения, был ли второй SELECT до или после финального COMMIT.

Мой вопрос, таким образом, почему порядок второго SELECT и UPDATE имеет значение. Я подозреваю, что там происходит что-то тонкое, чего я не понимаю, и я боюсь, что это укусит меня позже.

Есть подсказки?

Ответы [ 4 ]

2 голосов
/ 18 февраля 2009

по умолчанию транзакции ПРОЧИТАЮТСЯ:

"Указывает, что общие блокировки удерживаются во время чтения данных, чтобы избежать грязного чтения, но данные могут быть изменены до конца транзакции, что приведет к неповторяемым чтениям или фантомным данным. Этот параметр используется по умолчанию для SQL Server. «

http://msdn.microsoft.com/en-us/library/aa259216.aspx

Я думаю, что вы ничего не получаете в выборе, потому что запись все еще помечена как грязная. Вам нужно изменить уровень изоляции транзакции ИЛИ, сначала я делаю обновление, а затем читаю запись, но для этого необходимо пометить запись с уникальным значением (я использую getdate () для пакетов но GUID будет то, что вы, вероятно, хотите использовать).

1 голос
/ 18 февраля 2009

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

С тех пор я взял бесплатный MS SQL Server Management Studio Express, и все работает отлично. Для тех, кто заинтересован, URL для SMSE здесь:

MS SQL Server SMSE

Не забудьте также установить пакет обновления MSXML6:

MSXML с пакетом обновления 1

Так что, в этом случае, я абсолютно плох. : - (

Большое спасибо и спасибо вам, ребята, за ваши ответы. Вы помогли мне подтвердить, что то, что я делал, должно работать, что привело меня к изменению, которое я должен был внести, чтобы фактически «решить» проблему. Большое спасибо!

1 голос
/ 18 февраля 2009

Хотя я не отвечаю прямо на ваш вопрос здесь, вместо того, чтобы заново изобретать колесо и усложнять себе жизнь, если вы, конечно, не наслаждаетесь им ;-), могу ли я предложить вам взглянуть на использование SQL Server Service Broker.

Предоставляет существующую платформу для использования очередей и т. Д.

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

Ссылка на брокера услуг

Теперь вернемся к вопросу, я не могу воспроизвести вашу проблему, так как вы увидите, что если вы выполните приведенный ниже код, данные будут возвращены независимо от порядка оператора select / update.

Итак, ваш пример выше.

create table #MSG_Q
(id int identity(1,1) primary key,status int)
insert into #MSG_Q select 0

DECLARE @queueid INT
BEGIN TRANSACTION TRAN1
SELECT TOP 1 @queueid = id FROM #MSG_Q WITH (updlock, readpast) WHERE #MSG_Q.status=0
UPDATE #MSG_Q SET status=1 WHERE id=@queueid
SELECT TOP 1 * FROM #MSG_Q WHERE #MSG_Q.id=@queueid
COMMIT TRANSACTION TRAN1

select * from #MSG_Q
drop table #MSG_Q

Возвращает результаты (1,1) и (1,1)

Теперь поменяйте местами порядок выписок.

create table #MSG_Q
(id int identity(1,1) primary key,status int)    
insert into #MSG_Q select 0

DECLARE @queueid INT
BEGIN TRANSACTION TRAN1
SELECT TOP 1 @queueid = id FROM #MSG_Q WITH (updlock, readpast) WHERE #MSG_Q.status=0
SELECT TOP 1 * FROM #MSG_Q WHERE #MSG_Q.id=@queueid
UPDATE #MSG_Q SET status=1 WHERE id=@queueid
COMMIT TRANSACTION TRAN1

select * from #MSG_Q
drop table #MSG_Q

Результат: (1,0), (1,1) как ожидалось.

Возможно, вы могли бы еще более квалифицировать вашу проблему?

0 голосов
/ 28 марта 2009

Еще один момент - включение «SET NOCOUNT ON» в хранимую процедуру исправило ошибки для всех клиентов ODBC. Очевидно, что подсчет строк для первого выбора приводил в замешательство клиентов ODBC, и указание SQL Server не возвращать это значение заставляет вещи работать идеально ...

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