2010-09-04 21 views
29

Biorąc pod uwagę tabelę działającą jak kolejka, jak najlepiej skonfigurować tabelę/zapytania tak, aby wielu klientów mogło jednocześnie przetwarzać kolejki?Używanie programu SQL Server jako kolejki DB z wieloma klientami

Na przykład poniższa tabela zawiera polecenie, które pracownik musi przetworzyć. Po zakończeniu procesu roboczego ustawiana jest wartość true.

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

Klienci mogą otrzymać jedną komendę do pracy w taki sposób:

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

Jednakże, jeśli istnieje wiele pracowników, każdy próbuje dostać wiersz z ID = 2. Tylko ten pierwszy dostanie pesymistyczny zamek, reszta będzie czekać. Wtedy jeden z nich otrzyma wiersz 3 itd.

Jakie zapytanie/konfiguracja pozwoliłoby każdemu klientowi pracowniczemu uzyskać inny wiersz i jednocześnie pracować nad nimi?

EDIT:

Kilka odpowiedzi sugerują, wariacje na temat korzystania z samej tabeli, aby nagrać państwowych w procesie. Sądziłem, że nie będzie to możliwe w ramach jednej transakcji. (Czyli o co chodzi z aktualizacją stanu jeśli żaden inny pracownik będzie go zobaczyć aż txn stawia?) Być może propozycja to:

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

Czy w ten sposób ludzie zwykle podejść do tego problemu? Wydaje mi się, że w miarę możliwości problem byłby lepiej rozwiązywany przez DB.

+0

duplikat ... –

+3

proszę wskazać mi oryginał, ponieważ SO nie polecił dupe. – Synesso

+2

Po co mieć kłopot z odtworzeniem całej tej funkcjonalności, gdy jest już dostępna dla dowolnego serwera Windows w postaci Microsoft Message Queuing (MSMQ)? Wykorzystaj to, co jest dostępne - nie twórz ciągle nowego koła! –

Odpowiedz

39

polecam przejść Using tables as Queues. Odpowiednio zaimplementowane kolejki mogą obsłużyć tysiące równoczesnych użytkowników i serwisować nawet do 1/2 miliona operacji kolejkowania/wycofywania na minutę. Do SQL Server 2005 rozwiązanie było kłopotliwe i wymagało mieszania SELECT i UPDATE w pojedynczej transakcji i dawało właściwą kombinację wskazówek blokowania, tak jak w artykule połączonym gbn. Luckly od SQL Server 2005 wraz z pojawieniem się klauzula OUTPUT, znacznie bardziej eleganckie rozwiązanie jest dostępne, a teraz MSDN zaleca stosowanie OUTPUT clause:

Można używać funkcji w aplikacjach wykorzystujących tabele kolejek, lub przytrzymaj zestawy wyników pośrednich. Oznacza to, że aplikacja stale dodając lub usuwanie wierszy z tabeli

Zasadniczo istnieją 3 części układanki trzeba uzyskać prawo, aby to działało w sposób wysoce jednoczesnego sposób:

1) Musisz zdekompletować atomowo. Trzeba znaleźć wiersz, skipp żadnych zablokowane wiersze i oznaczyć ją jako „rozkolejkowywana” w jednym, operacji atomowej, a to jest, gdy klauzula OUTPUT wchodzi w grę:

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

2) Aby koniecznością uporządkuj tabelę za pomocą najdalszego od lewej strony klucza indeksowego w kolumnie PROCESSED. Jeśli użyto klucza ID, przenieś go jako drugą kolumnę w kluczu klastrowym. Debata, czy zachować klucz nieklastrowanym na kolumnie ID jest otwarta, ale ja zdecydowanie faworyzują nie mając żadnych wtórnych indeksy non-skupione na kolejkach:

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

3) Nie wolno zapytać tę tabelę dowolne inne środki, ale przez Dequeue. Próba wykonania operacji Peek lub próba użycia tabeli jako kolejki i jako sklepu będzie bardzo prawdopodobne, że doprowadzi do zakleszczenia i znacznie spowolni przepustowość.

Połączenie likwidacji atomowej, wskazówka READPAST przy wyszukiwaniu elementów do usunięcia i po lewej stronie klucza na indeksie klastrowym w oparciu o bit przetwarzający zapewnia bardzo wysoką przepustowość w bardzo równoczesnym obciążeniu.

+0

To ["Używanie tabel jako kolejki"] (http : //rusanu.com/2010/03/26/using-tables-as-queues/) artykuł jest czystym złotem dla tych z nas, którzy muszą zaimplementować kolejki nieodpowiednie dla Service Broker (np. oczekująca kolejka). Dzięki mang. –

+0

Jak zmienić harmonogram oczekującej wiadomości na przetwarzanie później, gdy element message_handler chce wycofać zmiany (w ramach tej samej transakcji)? – dariol

+0

@ dario-g: nie można celowo przywrócić przetwarzania wiadomości. –

0

Zamiast używać wartość logiczną dla przetworzony można użyć int aby określić stan polecenia:

1 = not processed 
2 = in progress 
3 = complete 

Każdy pracownik będzie wtedy dostać następny wiersz przetworzonym = 1, aktualizacja przetwarzane w celu 2, wówczas Zacznij pracę. Gdy praca w pełnym przetwarzaniu jest aktualizowana do 3. Takie podejście umożliwiłoby także rozszerzenie innych wyników przetworzonych, na przykład zamiast definiowania, że ​​pracownik jest kompletny, można dodawać nowe statusy "zakończone pomyślnie" i "ukończone z błędami"

+0

Dziękuję. Zobacz moją edycję. Czy myliłem się z twoją sugestią? – Synesso

+0

Masz rację, będziesz potrzebować oddzielnych transakcji, aby inni pracownicy mogli zobaczyć aktualizację, która powinna być domyślnym zachowaniem - dlaczego miałbyś zachować transakcję otwartą, podczas gdy pracownik przetwarza polecenie? Widzę parytet w tym, że sam pracownik jest zasadniczo transakcją, ale jest to prawie na pewno lepsze kodowanie niż korzystanie z transakcji serwera SQL. – Macros

0

Prawdopodobnie lepszym rozwiązaniem będzie użycie przetworzonej kolumny trisSate wraz z kolumną wersja/znacznik czasu. Trzy wartości w przetwarzanej kolumnie będą wtedy wskazywać, czy wiersz jest przetwarzany, przetwarzany czy nieprzetworzony.

Na przykład

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

chwycić Top 1 nieprzetworzonego wiersz, ustaw status na underprocessing i ustawić stan z powrotem przetwarzane, gdy rzeczy są zrobione. Oprzyj swój status aktualizacji na kolumnach Wersja i klucz podstawowy. Jeśli aktualizacja się nie powiedzie, ktoś już tam był.

Można również dodać identyfikator klienta, aby po śmierci klienta mógł on zostać zrestartowany, obejrzeć ostatni wiersz, a następnie zacząć od miejsca, w którym się znajdował.

+0

Dzięki. Zobacz moją edycję. Ponadto dla ciągłej dostępności chciałbym, aby każdy dostępny klient wznowił nieudane zadanie - nie tylko takie, które się nie powiodło. – Synesso

0

Chciałbym trzymać się z daleka od bałaganu z zamkami w stole. Po prostu utwórz dwie dodatkowe kolumny, takie jak IsProcessing (bit/boolean) i ProcessingStarted (datetime). Kiedy pracownik ulega awarii lub nie aktualizuje swojego wiersza po przekroczeniu limitu czasu, możesz poprosić innego pracownika o przetworzenie danych.

+0

Dzięki. Zobacz moją edycję. Czy to rozwiązanie wymaga początkowej aktualizacji poza główną transakcją? – Synesso

+0

Dlaczego chcesz skorzystać z transakcji? – ZippyV

0

Jednym ze sposobów jest oznaczenie wiersza pojedynczą instrukcją aktualizacji.Jeśli czytasz status w klauzuli where i zmieniasz go w klauzuli set, żaden inny proces nie może wejść pomiędzy, ponieważ wiersz zostanie zablokowany. Np

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 

używa rowcount zaktualizować jeden wiersz w większości. Jeśli nie zostanie znaleziony żaden wiersz, @pickup_id będzie -1.

8

Moja odpowiedź tutaj pokazuje, jak korzystać z tabel w kolejkach ... SQL Server Process Queue Race Condition

Zasadniczo trzeba „dulka, READPAST, UPDLOCK” podpowiedzi

+0

Wskazówki dotyczące blokowania są przydatne do ochrony zmian między wyborem a aktualizacją. Nie są one wymagane ani użyteczne, jeśli pojawi się kolejka z pojedynczą instrukcją aktualizacji – Andomar

+0

@Andomar: jest to potrzebne: 100% bezpiecznej współbieżności dla czytników i autorów ... – gbn

1

Jeśli chcesz serializować operacje dla wielu klientów, możesz po prostu użyć blokad aplikacji.

BEGIN TRANSACTION 

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

-- perform operation 

EXEC sp_releaseapplock @resource = 'app_token' 

COMMIT TRANSACTION 
Powiązane problemy