Вернуть незаблокированные строки в запросе "выберите топ n" - PullRequest
6 голосов
/ 03 сентября 2010

Мне нужна таблица базы данных MsSql и еще 8 (идентичных) процессов, которые параллельно обращаются к одной и той же таблице - выбирают top n, обрабатывают эти n строк и обновляют столбец этих строк. Проблема в том, что мне нужно выбрать и обработать каждую строку только один раз. Это означает, что если один процесс попал в базу данных и выбрал верхние n строк, то при появлении второго процесса он должен найти эти строки заблокированными и выбрать строки от n до 2 * n строк и т. Д.

Можно ли установить блокировку на некоторые строки, когда вы выбираете их и когда кто-то запрашивает верхние n строк, которые заблокированы, чтобы вернуть следующие строки, а не ждать заблокированных? Похоже на длинный выстрел, но ...

Еще одна вещь, о которой я подумал - может быть, не такая элегантная, но звучит просто и безопасно, - это иметь в базе данных счетчик экземпляров, которые делали выборки в этой таблице. Первый поступающий экземпляр увеличивает счетчик и выбирает top n, следующий - увеличивает счетчик и выбирает строки от n * (i-1) до n * i и т. Д. ...

Звучит ли это как хорошая идея? У вас есть предложения получше? Любая мысль высоко ценится!

Спасибо за ваше время.

Ответы [ 4 ]

6 голосов
/ 03 сентября 2010

Вот пример Я недавно писал в блоге :

Подсказка READPAST гарантирует, что несколько процессов не будут блокировать друг друга при опросе записей для обработки. Кроме того, в этом примере у меня есть битовое поле для физической «блокировки» записи - при необходимости может быть дата-время.

DECLARE @NextId INTEGER
BEGIN TRANSACTION

-- Find next available item available
SELECT TOP 1 @NextId = ID
FROM QueueTable WITH (UPDLOCK, READPAST)
WHERE IsBeingProcessed = 0
ORDER BY ID ASC

-- If found, flag it to prevent being picked up again
IF (@NextId IS NOT NULL)
    BEGIN
        UPDATE QueueTable
        SET IsBeingProcessed = 1
        WHERE ID = @NextId
    END

COMMIT TRANSACTION

-- Now return the queue item, if we have one
IF (@NextId IS NOT NULL)
    SELECT * FROM QueueTable WHERE ID = @NextId
2 голосов
/ 03 сентября 2010

Самый простой способ - использовать блокировку строки :

BEGIN TRAN

SELECT *
FROM authors
WITH (HOLDLOCK, ROWLOCK)
WHERE au_id = '274-80-9391'

/* Do all your stuff here while the record is locked */

COMMIT TRAN

Но если вы получаете доступ к своим данным и затем закрываете соединение, вы не сможете использовать этоmethod.

Сколько времени вам понадобится для блокировки строк?На самом деле наилучшим способом может быть, как вы говорите, размещение счетчика в выбранных вами строках (лучше всего делать с помощью предложения OUTPUT внутри UPDATE).

0 голосов
/ 03 сентября 2010

РЕДАКТИРОВАТЬ: ааа, не важно, вы работаете в автономном стиле. Как насчет этого:

UPDATE TOP (@n) QueueTable SET Locked = 1
OUTPUT INSERTED.Col1, INSERTED.Col2 INTO @this
WHERE Locked = 0

<do your stuff>

Возможно, вы ищете READPAST подсказку?

<begin or save transaction>

INSERT INTO @this (Col1, Col2) 
SELECT TOP (@n) Col1, Col2 
FROM Table1 WITH (ROWLOCK, HOLDLOCK, READPAST)

<do your stuff>

<commit or rollback>
0 голосов
/ 03 сентября 2010

Лучше всего, если вы хотите выбрать записи таким способом, - использовать счетчик в отдельной таблице.

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

Если вам нужна рука, пишущая таблицы и процедуры, которые сделают это (просто и безопасно, как вы это выразили!), Просто спросите.

...