Использование SQL Server в качестве очереди БД с несколькими клиентами - PullRequest
32 голосов
/ 04 сентября 2010

Учитывая таблицу, которая действует как очередь, как я могу наилучшим образом настроить таблицу / запросы так, чтобы несколько клиентов обрабатывали из очереди одновременно?

Например, в таблице ниже указана команда, которую должен обработать работник. Когда работник завершит работу, он установит для обработанного значения значение true.

| ID | COMMAND | PROCESSED |
|  1 | ...     | true      |
|  2 | ...     | false     |
|  3 | ...     | false     |

Клиенты могут получить одну команду для работы следующим образом:

select top 1 COMMAND 
from EXAMPLE_TABLE 
with (UPDLOCK, ROWLOCK) 
where PROCESSED=false;

Однако, если есть несколько рабочих, каждый пытается получить строку с ID = 2. Только первый получит пессимистическую блокировку, остальные будут ждать. Тогда один из них получит строку 3 и т. Д.

Какой запрос / конфигурация позволят каждому работающему клиенту получать разные строки и работать с ними одновременно?

EDIT:

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

# start transaction
update to 'processing'
# end transaction
# start transaction
process the command
update to 'processed'
# end transaction

Так люди обычно подходят к этой проблеме? Мне кажется, что эта проблема будет лучше решена БД, если это возможно.

Ответы [ 7 ]

51 голосов
/ 04 сентября 2010

Я рекомендую вам перейти на Использование таблиц в качестве очередей .Правильно реализованные очереди могут обрабатывать тысячи одновременно работающих пользователей и обслуживать до 1/2 миллиона операций постановки / удаления очереди в минуту.До SQL Server 2005 решение было громоздким и включало смешивание SELECT и UPDATE в одной транзакции и обеспечивало правильное сочетание подсказок блокировки, как в статье, на которую ссылается gbn.К счастью, начиная с SQL Server 2005 с появлением предложения OUTPUT, доступно гораздо более элегантное решение, и теперь MSDN рекомендует использовать предложение OUTPUT :

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

По сути, есть 3 части головоломки, которые нужно правильно настроить, чтобы это работало в высокой степени одновременно:

1) Вы должны разряжать атомарно.Вы должны найти строку, пропустить все заблокированные строки и пометить ее как «исключенную из очереди» в одной атомарной операции, и вот здесь вступает в игру предложение OUTPUT:

with CTE as (
  SELECT TOP(1) COMMAND, PROCESSED
  FROM TABLE WITH (READPAST)
  WHERE PROCESSED = 0)
UPDATE CTE
  SET PROCESSED = 1
  OUTPUT INSERTED.*;

2)Вы должны структурировать свою таблицу с помощью крайнего левого ключа кластеризованного индекса в столбце PROCESSED.Если в качестве ID использовался первичный ключ, то переместите его как второй столбец в кластерном ключе.Дискуссия о том, следует ли хранить некластеризованный ключ в столбце ID, открыта, но я настоятельно рекомендую, чтобы не имел какие-либо вторичные некластеризованные индексы над очередями:

CREATE CLUSTERED INDEX cdxTable on TABLE(PROCESSED, ID);

3Вы не должны запрашивать эту таблицу другими способами, кроме как с помощью Dequeue.Попытка выполнения операций Peek или использование таблицы в качестве очереди и в качестве хранилища очень вероятно приведут к взаимоблокировкам и значительно снизят пропускную способность.

Комбинация атомарной очереди, подсказки READPAST при поиске элементов для удаления очереди и крайнего левого ключа в кластеризованном индексе на основе бита обработки обеспечивают очень высокую пропускную способность при высокой параллельной загрузке.

8 голосов
/ 04 сентября 2010

Мой ответ здесь показывает, как использовать таблицы в качестве очередей ... Состояние гонки очереди процесса SQL Server

Вам нужны подсказки "ROWLOCK, READPAST, UPDLOCK"

1 голос
/ 08 января 2014

Если вы хотите сериализовать свои операции для нескольких клиентов, вы можете просто использовать блокировки приложений.

BEGIN TRANSACTION

EXEC  sp_getapplock @resource = 'app_token', @lockMode = 'Exclusive'

-- perform operation

EXEC  sp_releaseapplock @resource = 'app_token'

COMMIT TRANSACTION
0 голосов
/ 04 сентября 2010

Один из способов - пометить строку одним оператором обновления. Если вы прочитаете состояние в предложении where и измените его в предложении set, никакой другой процесс не сможет встать между ними, потому что строка будет заблокирована. Например:

declare @pickup_id int
set @pickup_id = 1

set rowcount 1

update  YourTable
set     status = 'picked up'
,       @pickup_id = id
where   status = 'new'

set rowcount 0

return @pickup_id

Используется rowcount для обновления не более одной строки. Если строка не найдена, @pickup_id будет -1.

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

Я бы держалась подальше от замков в столе.Просто создайте два дополнительных столбца, таких как IsProcessing (bit / boolean) и ProcessingStarted (datetime).Когда работник выходит из строя или не обновляет свою строку после истечения времени ожидания, вы можете попросить другого работника попытаться обработать данные.

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

Возможно, лучшим вариантом будет использование обработанного столбца trisSate вместе со столбцом версии / метки времени.Затем три значения в обработанном столбце будут указывать, будет ли строка обрабатываться, обрабатываться или не обрабатываться.

Например

    CREATE TABLE Queue ID INT NOT NULL PRIMARY KEY,
    Command NVARCHAR(100), 
    Processed INT NOT NULL CHECK (Processed in (0,1,2) ), 
    Version timestamp)

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

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

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

Вместо того, чтобы использовать логическое значение для Обработано, вы можете использовать int для определения состояния команды:

1 = not processed
2 = in progress
3 = complete

Затем каждый рабочий получит следующую строку с Processed = 1, обновит Processed до 2 и начнет работу. Когда работа завершена Обработано обновлено до 3. Этот подход также позволит расширить другие обработанные результаты, например, вместо того, чтобы просто определить, что работник завершен, вы можете добавить новые статусы для «Успешно завершено» и «Завершено с ошибками»

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