2012-12-23 11 views
5

Poszukuję sposobu wybrania jednego wiersza tabeli jawnie dla jednego wątku. Napisałem robota, który działa z około 50 równoległymi procesami. Każdy proces musi zająć jeden wiersz ze stołu i przetworzyć go.Wybierz tylko jeden wiersz tabeli na wysokim równoległym połączeniach

CREATE TABLE `crawler_queue` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
`url` text NOT NULL, 
`class_id` tinyint(3) unsigned NOT NULL, 
`server_id` tinyint(3) unsigned NOT NULL, 
`proc_id` mediumint(8) unsigned NOT NULL, 
`prio` tinyint(3) unsigned NOT NULL, 
`inserted` int(10) unsigned NOT NULL, 
PRIMARY KEY (`id`), 
KEY `proc_id` (`proc_id`), 
KEY `app_id` (`app_id`), 
KEY `crawler` (`class_id`,`prio`,`proc_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 

Teraz moje procesy wykonaj następujące czynności:

  • transakcja początek DB
  • zrobić select jak SELECT * FROM crawler_queue WHERE class_id=2 AND prio=20 AND proc_id=0 ORDER BY id LIMIT 1 FOR UPDATE
  • następnie zaktualizować ten wiersz z UPDATE crawler_queue SET server_id=1,proc_id=1376 WHERE id=23892
  • popełnić transakcja

Powinno to pomóc, aby żaden inny proces nie mógł pobrać wiersza, który jest jeszcze przetwarzany. Robi Wyjaśnij na wybranych pokazach

id select_type table   type possible_keys key  key_len ref rows Extra 
1 SIMPLE  crawler_queue ref proc_id,crawler proc_id 3  const 617609 Using where 

Ale procesy wydają się powodować zbyt wysoką równoległości, bo czasami widzę dwa rodzaje błędów/ostrzeżeń w moim dzienniku (co 5 minut lub tak):

mysqli::query(): (HY000/1205): Lock wait timeout exceeded; try restarting transaction (in /var/www/db.php l 
ine 81) 

mysqli::query(): (40001/1213): Deadlock found when trying to get lock; try restarting transaction (in /var/www/db.php line 81) 

Moje pytanie brzmi: czy ktokolwiek może wskazać mi właściwy kierunek, aby zminimalizować te problemy z blokowaniem? (W stanie produkcyjnej, równoległość będzie 3-4 razy większa niż obecnie, więc zakładam, że nie będzie problemów znacznie bardziej blokujące)

EDIT 29.12.2012: I zmodyfikowane SELECT użyć indeksu crawler podpowiedź USE INDEX(crawler). Moim problemem są teraz limity czasu blokady (zakleszczenia znikają).

EDIT 2012-12-31: EXPLAIN z USE INDEX() pokazuje teraz (liczba wierszy jest wyższa, ponieważ tabela zawiera więcej danych teraz.):

id select_type table   type possible_keys key  key_len ref    rows  Extra 
1 SIMPLE  crawler_queue ref proc_id,crawler crawler 5  const,const,const 5472426 Using where 

Odpowiedz

0

Lepszym rozwiązaniem byłoby wykonanie aktualizacji i całkowite pominięcie zaznaczenia. Następnie możesz użyć last_insert_id(), aby pobrać zaktualizowany element. Powinno to pozwolić na całkowite pominięcie blokowania podczas jednoczesnej aktualizacji. Po zaktualizowaniu rekordu można rozpocząć jego przetwarzanie, ponieważ nigdy nie zostanie ono ponownie wybrane przez dokładnie to samo zapytanie, ponieważ nie wszystkie warunki początkowe są już pasujące.

Myślę, że powinno to pomóc w rozwiązaniu wszystkich problemów związanych z blokowaniem i powinno pozwolić na jednoczesne uruchomienie dowolnej liczby procesów.

PS: Aby wyjaśnić, mówię o update ... limit 1, aby upewnić się, że aktualizujesz tylko jeden wiersz.

EDIT: Solution

jest poprawna jak wskazano poniżej.

+1

Przyjemny pomysł, ale 'LAST_INSERT_ID()' zwróci wartość tylko wtedy, gdy dane "WSTAW" lub "AKTUALIZACJA" zwiększą kolumnę autoinkrementacji: ** EDYTUJ ** Podaję http://stackoverflow.com/questions/ 1388025/how-to-get-of-the-last-updated-row-in-mysql a spróbuj – rabudde

+0

Z jakiegoś powodu uzyskałem wartość last_insert_id, kiedy testowałem, ale to mnie oszukało (wyglądało to jak poprawne, ale tak nie było). Uważam, że rozwiązanie opisane w tym pytaniu dotyczącym SO jest drogą do zrobienia. Zaktualizuję też odpowiedź. – Xnoise

0

Z tego co mogę powiedzieć, że ten problem Cię "Wobec tego dwa wątki są vyying dla tego samego wiersza w tabeli i obaj nie mogą go mieć. Ale nie ma eleganckiego sposobu na to, by baza danych mówiła "nie, nie możesz tego mieć, znajdź inny wiersz", a więc dostaniesz błędy. Nazywa się to rywalizacją zasobów.

Podczas wykonywania bardzo równoległych prac, takich jak ten, jednym z najprostszych sposobów na zmniejszenie problemów opartych na rywalizacji jest całkowite wyeliminowanie rywalizacji poprzez wymyślenie sposobu, w jaki wszystkie wątki będą wiedzieć, w których rzędach mają pracować. z wyprzedzeniem. Następnie mogą się zablokować bez konieczności rywalizowania o zasoby, a twoja baza danych nie musi rozstrzygać sporów.

Jak najlepiej to zrobić? Zwykle ludzie wybierają pewien rodzaj schematu id wątków i używają arytmetyki modulo, aby określić, które wątki otrzymują wiersze. Jeśli masz 10 wątków, wątek 0 otrzymuje wiersz 0, 10, 20, 30 itd. Wątek 1 otrzymuje 1, 11, 21, 31 itd.

Ogólnie, jeśli masz NUM_THREADS, każdy z wątków wybierze Identyfikatory, które są THREAD_ID + i * NUM_THREADS z bazy danych i pracują nad nimi.

Wprowadziliśmy problem polegający na tym, że wątki mogą zgasnąć lub zginąć, a na końcu mogą pojawić się wiersze w bazie danych, które nigdy nie zostaną dotknięte.Istnieje kilka rozwiązań tego problemu, z których jednym jest uruchomienie "czyszczenia" po zakończeniu większości/wszystkich wątków, w których wszystkie wątki przechwytują fragmenty, które mogą, i indeksują je, dopóki nie pozostały nieuszukiwane adresy URL. Możesz uzyskać bardziej wyrafinowane i mieć kilka wątków czyszczących nieprzerwanie działających, lub każdy wątek od czasu do czasu wykonywać obowiązki czyszczenia, itp.

3

Twój raport WYPEŁNIAj pokazuje, że używasz tylko jednokolumnowego indeksu proc_id, a zapytanie zbadać ponad 600 tysięcy wierszy. Byłoby chyba lepiej, gdyby optymalizator wybrał indeks crawler.

InnoDB może blokować wszystkie wiersze 600K, a nie tylko wiersze pasujące do pełnego warunku w klauzuli WHERE. InnoDB blokuje wszystkie badane wiersze, aby upewnić się, że współbieżne zmiany nie zostaną zapisane w binlog w niewłaściwej kolejności.

Rozwiązaniem jest użycie indeksu do zawężenia zakresu badanych wierszy. To prawdopodobnie pomoże ci nie tylko szybciej znaleźć wiersze, ale także uniknąć blokowania dużych zakresów wierszy. Powinien pomóc tutaj indeks crawler, ale nie jest od razu jasne, dlaczego nie korzysta z tego indeksu.

Być może trzeba będzie uzyskać ANALYZE TABLE, aby zaktualizować statystyki tabeli InnoDB, aby dowiedzieć się o indeksie crawler przed użyciem tego indeksu w planie optymalizacji. ANALIZA TABELA to niedroga operacja.

Inną opcją jest użycie indeksu podpowiedź:

SELECT * FROM crawler_queue USE INDEX(crawler) ... 

Informuje optymalizator do korzystania z tego indeksu, a nie biorą pod uwagę inne wskaźniki dla tego zapytania. Wolę unikać wskazówek do indeksu, ponieważ optymalizator zazwyczaj sam podejmuje właściwe decyzje, a użycie podpowiedzi w kodzie oznacza, że ​​mogę zmusić optymalizatora, aby nie uwzględniał indeksu, który utworzę w przyszłości, który w przeciwnym razie wybrałby .


Z większą ilością wyjaśnień, jasne jest, że używasz RDBMS jako FIFO. To nie jest efektywne wykorzystanie RDBMS. W tym celu dostępne są technologie kolejkowania komunikatów.

Zobacz także:

+0

Hej, Bill, właśnie to zrobiłem (przepraszam, że nie zaktualizowałem mojego pytania, dam +1). Ale co dziwne, to wyjaśnienie pokazało czasami użycie 'crawler' zamiast' proc_id'. Ale na razie wymuszam użycie indeksu 'crawler'. Wydam również polecenie tabeli analizującej. Dziękujemy – rabudde

+0

Sprawdź pole 'rows' w wyjściu EXPLAIN. W związku z tym, że liczba przeanalizowanych wierszy powinna być niższa, mam nadzieję. –

+0

Nie, nie ma (patrz wyżej) – rabudde

Powiązane problemy