Нужна ли мне подсказка UPDLOCK в этом SP? Чтение строки очереди данных более одного раза

Моя схема БД: Job -> (Many) JobData -> (Many) Results. Задание разбивается на несколько строк JobData, чтобы несколько потоков могли обрабатывать «фрагменты» задания (каждую строку JobData). Затем потоки обработки вставляют строки Result для каждой из строк JobData, которые он обрабатывает.

Один поток пытается прочитать результаты в цикле, пока не будут прочитаны все результаты. Обратите внимание, что в то же время потоки могут обрабатывать строки JobData (пока все не будут обработаны) и вставлять результаты. Таким образом, в цикле каждый вызов этого SP проходит в JobKey... но каким-то образом с этого кода результаты считываются несколько раз (очень низкая частота... т.е. ‹ 3 строки на 8000 строк результатов), но воспроизводятся только случайным образом.

Я добавил комментарий ниже в коде рядом с моим заявлением UPDATE TOP относительно UPDLOCK. Заранее спасибо.

BEGIN TRANSACTION

-- Create a temp table TO store the select results
DECLARE @UnReadResults TABLE
(
    ResultKey uniqueidentifier
)

-- Wouldn't expect a UPDLOCK is needed since UPDATE statements create exclusive lock anyway?  Do I need to SELECT WITH UPDLOCK first, then do the UPDATE statement?

UPDATE TOP ( @pageSize ) Result
SET rResultRead = 1
OUTPUT INSERTED.rKey INTO @UnReadResults
FROM Result r INNER JOIN JobData AS jd ON jd.jdKey = r.rJobDataKey
WHERE jd.jdJobKey = @jobKey AND rResultRead = 0

-- Just return Job (always the same), JobData (could vary if chunk of result rows
-- spans multiple JobDatas) and Results that I successfully 'grabbed' by joining
-- to my temp table

SELECT j.jKey, j.jStatus, j.jResultsRead, 
   jd.jdKey, jd.jdJobKey, jd.jdDateStart, jd.jdDateComplete, jd.jdStatus,
   r.rKey, r.rProfileKey, r.rProfileAuthID, r.rResult, r.rReadLock, r.rReadAttempts
FROM Job j
    INNER JOIN JobData jd
        ON jKey = jdJobKey
    INNER JOIN Result r
        ON jdKey = rJobDataKey
    INNER JOIN @UnReadResults urr
        on rKey = urr.ResultKey

COMMIT TRANSACTION

РЕДАКТИРОВАТЬ: Просто хотел поставить то, что у меня есть сейчас, на основе ответа @gbn. Исключая весь SP, но оператор UPDATE теперь имеет следующий формат:

UPDATE TOP ( @pageSize ) Result
SET rResultRead = 1, rReadLock = @lock, rReadAttempts = rReadAttempts + 1
OUTPUT INSERTED.rKey INTO @UnReadResults
FROM Result r WITH ( ROWLOCK, READPAST, UPDLOCK ) INNER JOIN JobData AS jd ON jd.jdKey = r.rJobDataKey
WHERE jd.jdJobKey = @jobKey AND rResultRead = 0

Успешно работает уже несколько дней, так что, надеюсь, я решил проблему. Если это не похоже на правильное использование табличных подсказок, не стесняйтесь, дайте мне знать.


person Terry    schedule 19.01.2012    source источник
comment
Хм, может быть дубликат stackoverflow.com /вопросы/939831/. Проверка.   -  person Terry    schedule 19.01.2012
comment
да, это таблица как ответ на шаблон очереди. Вы хорошо используете OUTPUT: просто добавьте 3 подсказки (ROWLOCK, READPAST, UPDLOCK)   -  person gbn    schedule 19.01.2012


Ответы (1)


Ваше ОБНОВЛЕНИЕ на самом деле SELECT, за которым следует UPDATE, потому что у вас есть JOIN и WHERE.

Вам нужны дополнительные подсказки в соответствии с другим ответом, который вы отметили (это мой ответ)

  1. Заблокировать только строки: ROWLOCK
  2. Пропустить заблокированные строки: READPAST
  3. Увеличить продолжительность и изоляцию блокировок: UPDLOCK
person gbn    schedule 19.01.2012
comment
Итак, вы говорите, даже для простых обновлений, если у меня есть что-то вроде UPDATE Table SET Field = 1 WHERE Field = 0, это «SELECT», и мне нужно изменить его на UPDATE Table WITH (UPDLOCK) SET Field = 1 WHERE Поле = 0? - person Terry; 24.01.2012
comment
Вопрос о SELECT и нужны ли мне подсказки. БД — это Job-›JobData. Строка JobData — это фрагмент данных, который поток может захватить для обработки. Каждый поток вызывает «complete» в своей собственной строке JobData, но внутри SP мне нужно затем установить строку Job как завершенную, если каждая строка JobData помечена как завершенная. 1) ОБНОВИТЬ JobData SET jdStatus = @status WHERE jdKey = {jobDataKey}, затем 2) ЕСЛИ НЕ СУЩЕСТВУЕТ( SELECT jdKey FROM JobData WHERE jdJobKey = {jobKey} AND ( jdStatus = 0 /* New / ИЛИ jdStatus = 1/ Обработка */ ) ) НАЧАТЬ ОБНОВЛЕНИЕ Job SET jStatus = ( SELECT MAX(jdStatus) FROM JobData WHERE jdJobKey = {jobKey} ) - person Terry; 24.01.2012
comment
@Terry: после выделения постобработка ОБНОВЛЕНИЙ и т. д. не нуждается в подсказке. Только распределение ключей. Или я неправильно понял? - person gbn; 24.01.2012
comment
Возможно, вас неправильно поняли, хотя думаю, что ваши знания SQL намного выше моих. Должен ли я начать новый вопрос и опубликовать весь SP? Или написать вам напрямую, или как я могу попытаться показать вам свою озабоченность? - person Terry; 24.01.2012
comment
Еще один общий вопрос. В многопоточной очереди БД... если я хочу гарантировать обновление таблицы и не хочу, чтобы возникали взаимоблокировки (в случае, если два потока пытаются обновить одновременно)... вы просто ставите UPDATE Table WITH ( БЛОКИРОВКА, БЛОКИРОВКА)? И если два потока попытаются вызвать один и тот же SP (или даже если два разных SP попытаются обновить одну и ту же строку), будет ли это гарантировать только взаимоисключающие действия без ущерба для системы из-за введения взаимоблокировок? - person Terry; 24.01.2012
comment
@ Терри: трудно сказать. Если вы имеете в виду обновление распределения, вам нужны 3 подсказки, чтобы второй процесс пропустил блокировки из процесса 1. Если вы имеете в виду обновление постобработки, вы можете просто использовать ROWLOCK. В любом случае, у вас должна быть обработка взаимоблокировок в вашем коде clirnt. - person gbn; 24.01.2012