2010-09-03 13 views
6

Potrzebuję mieć tabelę bazy danych MsSql i kolejne 8 (identyczne) procesy, które uzyskują dostęp do tej samej tabeli równolegle - wybierając górny n, przetwarzając te n wierszy i aktualizując kolumna tych wierszy. Problem polega na tym, że muszę wybrać i przetworzyć każdy wiersz tylko raz. Oznacza to, że jeśli jeden proces dotarł do bazy danych i wybrał górne n wierszy, gdy nadejdzie drugi proces, powinien znaleźć te wiersze zablokowane i wybrać wiersze od n do 2 * n wierszy, i tak dalej ...Zwróć odblokowane wiersze w zapytaniu "wybierz najlepsze n".

Czy możliwe jest umieszczenie blokady w niektórych wierszach, gdy je wybierzesz, a kiedy ktoś zażąda górnych n wierszy, które są zablokowane, aby zwrócić kolejne wiersze i nie czekać na zablokowane? Wydaje się, jakby to był długi strzał, ale ...

Inną rzeczą, o której myślałem - może nie tak elegancko, ale brzmi prosto i bezpiecznie, jest posiadanie w bazie danych licznika dla instancji, które dokonały wyboru na tym stole. Pierwsza otrzymana instancja zwiększy licznik i wybierze górę n, następny zwiększy licznik i wybierze wiersze od n * (i-1) do n * i, i tak dalej ...

Czy ten dźwięk jak dobra ideea? Czy masz jakieś lepsze sugestie? Każda myśl jest bardzo ceniona!

Dzięki za poświęcony czas.

+3

Wygląda na to, że używasz tabeli jako kolejki? Możesz znaleźć [ten artykuł] (http: // rusanu.com/2010/03/26/using-tables-as-queues /) przez [Remus Rusanu] (http://stackoverflow.com/users/105929/remus-rusanu) przydatne. –

+0

Dzięki za link. Ciekawie było o tym wiedzieć, ale ponieważ muszę zachować wiersze po ich wybraniu (usunięcie ich nie jest opcją), to nie jest to w pełni zgodne z moim przypadkiem. – Diana

+1

Możesz mieć kolumnę 'przetworzonych' bitów i ewentualnie zastąpić' UPDATE' dla 'DELETE'. Jeśli zdecydujesz się użyć rozwiązania licznika [ta odpowiedź] (http://stackoverflow.com/questions/3453411/sql-server-auto-incrementation-that-allows-update-statements/3462957#3462957) może pomóc. –

Odpowiedz

6

Oto próbka I blogged about a while ago:

Podpowiedź READPAST co gwarantuje wiele procesów nie blokują się wzajemnie podczas odpytywania zapisów przetwarzać. Dodatkowo, w tym przykładzie mam pole bitowe do fizycznego "zablokowania" rekordu - w razie potrzeby może być datetime.

+0

Dzięki za anser, READPAST brzmi jak odpowiedź MsSql, której szukałem. Jeśli SELECT TOP 1 @NextId = ID jest wybraną top 3000 na kilka tabel z milionami rekordów, może to zająć trochę czasu. Czy jest "gwarantowane" przez READPAST, że jeśli mam inny wątek dokonujący dokładnie tego samego wyboru, drugi będzie przechwytywał kolejne niezablokowane wiersze? – Diana

+0

Przetestowałem twoje podejście na prostym stole testowym, działa jak urok. Ale na moim prawdziwym stole (duże przedwzmacniacz, mnóstwo indeksów i statystyk na nim), działa tylko czasami ... Innym razem, nie pomija zamkniętych wierszy, po prostu "zawiesza się", aż do poprzedniego wyboru blokowania wierszy jest ponad (Aby to przetestować dodałem aw "aitfor delay" 00: 00: 10 '"). Jakieś idee, dlaczego tak się dzieje? – Diana

+0

@Diana - Ile wierszy próbujesz zablokować dla jednego procesu? na przykład może blokada stołu jest zajęta (rowlocki zostaną uaktualnione do blokad stołowych powyżej pewnego progu). Jeśli mógłbyś opublikować przykładowy skrypt demonstrujący, jak to robisz (jak na przykład mój przykład dotyczył tylko jednego rekordu), byłoby świetnie - może być warty w połączonych pytaniach dla maksymalnej ekspozycji – AdaTheDev

2

Metoda najbardziej Najprostszym jest użycie row locking:

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 

Ale jeśli jesteś dostępu do swoich danych, a następnie zamknięcie połączenia, nie będzie mógł korzystać z tej metody.

Jak długo będziesz potrzebować blokować wiersze? Najlepszym sposobem może być tak, jak mówisz, umieszczenie licznika na wybranych wierszach (najlepiej zrobione przy użyciu klauzuli OUTPUT w ramach UPDATE).

+0

Muszę zamknąć połączenie po wybraniu, a przetworzenie partii wierszy zajmie trochę czasu (każdy wiersz określi wysyłanie wiadomości e-mail z załącznikiem 500kb). – Diana

+0

Powiedziałbym, że musisz zaimplementować własną metodę blokowania wierszy (takich jak licznik), ponieważ blokowanie wierszy może blokować żądania na tyle długo, że wystąpią błędy przekroczenia limitu czasu pojawiające się w innym miejscu. Komentarz Martina wygląda jak droga naprzód http://stackoverflow.com/questions/3636950/return-unlocked-rows-in-a-select-top-n-query#comment-3822218 – Codesleuth

0

Najlepszym pomysłem, jeśli chcesz wybrać rekordy w ten sposób, byłoby użycie licznika w osobnej tabeli.

Naprawdę nie chcesz blokować wierszy w produkcyjnej bazie danych wyłącznie na czas dłuższy, dlatego polecam użycie licznika. W ten sposób tylko jeden z twoich procesów byłby w stanie przechwycić ten numer licznika na raz (tak jak zablokuje się w trakcie aktualizacji), co da ci współbieżność, której potrzebujesz.

Jeśli potrzebujesz ręki do pisania tabel i procedur, które to wykonają (prosto i bezpiecznie, jak to umieścisz!) Po prostu zapytaj.

+0

Ok, cieszę się, że pomysł licznik wydaje się wystarczająco dobry i na pierwszy rzut oka nie widać większych przepływów. Dam mu więcej przemyśleń na dzień lub dłużej. Dziękuję za odpowiedź i pomoc! – Diana

0

EDYCJA: ahh, nevermind, pracujesz w rozłącznym stylu. Jak o tym:

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

<do your stuff> 

Może szukasz podpowiedź 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> 
Powiązane problemy