Mam tabelę innoDB, która rejestruje użytkowników online. Jest aktualizowany przy każdym odświeżeniu strony przez użytkownika, aby śledzić, na których stronach się znajduje i kiedy miał datę ostatniego dostępu do witryny. Następnie mam crona, który działa co 15 minut, aby usunąć stare rekordy.
Podczas próby uzyskania blokady znaleziono „Zakleszczenie”; spróbuj ponownie uruchomić transakcję przez ostatnią noc przez około 5 minut i wydaje się, że dzieje się tak podczas uruchamiania INSERTów w tej tabeli. Czy ktoś może zasugerować, jak uniknąć tego błędu?
=== EDYCJA ===
Oto uruchomione zapytania:
Pierwsza wizyta na stronie:
INSERT INTO onlineusers SET
ip = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
Odśwież stronę na każdej stronie:
UPDATE onlineusers SET
ips = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
WHERE id = 888
Cron co 15 minut:
DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND
Następnie rejestruje niektóre statystyki (np. Członkowie online, odwiedzający online).
Odpowiedzi:
Prostą sztuczką, która może pomóc w większości zakleszczeń, jest sortowanie operacji w określonej kolejności.
Otrzymujesz impas, gdy dwie transakcje próbują zablokować dwie blokady na przeciwnych zleceniach, tj .:
Jeśli oba działają w tym samym czasie, połączenie 1 zablokuje klucz (1), połączenie 2 zablokuje klucz (2) i każde połączenie będzie czekać, aż drugie zwolni klucz -> zakleszczenie.
Teraz, jeśli zmieniłeś swoje zapytania tak, że połączenia zablokowałyby klucze w tej samej kolejności, tj .:
impas nie będzie możliwy.
Oto co sugeruję:
Upewnij się, że nie masz innych zapytań blokujących dostęp do więcej niż jednego klucza na raz, z wyjątkiem instrukcji delete. jeśli to zrobisz (i podejrzewam, że tak), zamów ich GDZIE w (k1, k2, .. kn) w porządku rosnącym.
Napraw instrukcję usuwania, aby działała w porządku rosnącym:
Zmiana
Do
Inną rzeczą, o której należy pamiętać, jest to, że dokumentacja mysql sugeruje, że w przypadku impasu klient powinien ponowić próbę automatycznie. możesz dodać tę logikę do kodu klienta. (Powiedz, 3 próby tego konkretnego błędu przed rezygnacją).
źródło
Zakleszczenie występuje, gdy dwie transakcje oczekują na siebie w celu uzyskania blokady. Przykład:
Istnieje wiele pytań i odpowiedzi na temat impasu. Za każdym razem, gdy wstawiasz / aktualizujesz / lub usuwasz wiersz, tworzona jest blokada. Aby uniknąć zakleszczenia, musisz upewnić się, że równoczesne transakcje nie aktualizują wiersza w kolejności, która może spowodować zakleszczenie. Ogólnie rzecz biorąc, spróbuj uzyskać blokadę zawsze w tej samej kolejności, nawet w różnych transakcjach (np. Zawsze najpierw tabela A, a następnie tabela B).
Innym powodem impasu w bazie danych może być brak indeksów . Po wstawieniu / aktualizacji / usunięciu wiersza baza danych musi sprawdzić ograniczenia relacyjne, czyli upewnić się, że relacje są spójne. Aby to zrobić, baza danych musi sprawdzić klucze obce w powiązanych tabelach. Może to spowodować uzyskanie innej blokady niż zmodyfikowany wiersz. Pamiętaj, aby zawsze mieć indeks na kluczach obcych (i oczywiście kluczach podstawowych), w przeciwnym razie może to spowodować blokadę tabeli zamiast blokady wiersza . Jeśli nastąpi blokada stołu, rywalizacja o blokadę jest wyższa, a prawdopodobieństwo impasu rośnie.
źródło
Jest prawdopodobne, że instrukcja delete wpłynie na dużą część wszystkich wierszy w tabeli. Ostatecznie może to prowadzić do uzyskania blokady tabeli podczas usuwania. Trzymanie się blokady (w tym przypadku blokad wierszy lub stron) i uzyskiwanie większej liczby blokad zawsze stanowi ryzyko zakleszczenia. Jednak nie potrafię wyjaśnić, dlaczego instrukcja insert prowadzi do eskalacji blokady - może mieć to związek z dzieleniem / dodawaniem stron, ale ktoś, kto lepiej zna MySQL, będzie musiał tam wypełnić.
Na początek warto spróbować od razu uzyskać blokadę tabeli dla instrukcji delete. Zobacz BLOKADA TABEL i Problemy z blokowaniem tabel .
źródło
Możesz spróbować
delete
uruchomić to zadanie, najpierw wstawiając klucz każdego wiersza, który ma zostać usunięty, do tabeli tymczasowej, takiej jak ten pseudokodRozbicie go w ten sposób jest mniej wydajne, ale pozwala uniknąć konieczności trzymania blokady zakresu klawiszy podczas
delete
.Zmodyfikuj również swoje
select
zapytania, aby dodaćwhere
klauzulę z wyłączeniem wierszy starszych niż 900 sekund. Pozwala to uniknąć zależności od zadania cron i pozwala na rzadsze uruchamianie go.Teoria o zakleszczeniach: Nie mam zbyt dużego tła w MySQL, ale oto jest ...
delete
Zamierza utrzymać blokadę zakresu kluczy dla datetime, aby zapobiecwhere
dodawaniu wierszy pasujących do klauzuli w środku transakcji , a gdy znajdzie wiersze do usunięcia, spróbuje uzyskać blokadę na każdej modyfikowanej stronie.insert
Zamierza nabyć blokadę na stronie jest włożeniem, a następnie próbować pozyskać blokady. Zwykleinsert
będzie cierpliwie czekał na otwarcie tej blokady klucza, ale nastąpi impas, jeślidelete
spróbuje zablokować tę samą stronę, którejinsert
używa, ponieważdelete
potrzebuje tej strony iinsert
tej blokady. Nie wydaje się to jednak odpowiednie dla wstawek,delete
iinsert
używają zakresów czasu, które się nie pokrywają, więc może dzieje się coś innego.http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html
źródło
Jeśli ktoś nadal ma problem z tym problemem:
Napotkałem podobny problem, gdy 2 żądania uderzały jednocześnie o serwer. Nie było takiej sytuacji jak poniżej:
Byłem więc zdziwiony, dlaczego tak się dzieje.
Potem odkryłem, że między 2 tabelami istniała relacja rodzicielska z powodu klucza obcego. Kiedy wstawiałem rekord do tabeli podrzędnej, transakcja uzyskiwała blokadę w wierszu tabeli nadrzędnej. Zaraz potem próbowałem zaktualizować wiersz nadrzędny, który wyzwalał podniesienie blokady do WYŁĄCZNIE jednego. Ponieważ druga jednoczesna transakcja już posiadała blokadę SHARED, powodowała impas.
Zobacz: https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html
źródło
W przypadku programistów Java korzystających z Spring uniknąłem tego problemu, stosując aspekt AOP, który automatycznie ponawia próby transakcji, które napotykają przejściowe zakleszczenia.
Aby uzyskać więcej informacji, zobacz @RetryTransaction Javadoc.
źródło
Mam metodę, której elementy wewnętrzne są opakowane w transakcję MySqlTransaction.
Problem impasu pojawił się dla mnie, gdy uruchomiłem tę samą metodę równolegle z samym sobą.
Nie wystąpił problem z uruchomieniem pojedynczej instancji metody.
Kiedy usunąłem MySqlTransaction, byłem w stanie uruchomić metodę równolegle ze sobą bez żadnych problemów.
Po prostu dzieląc się swoim doświadczeniem, nic nie zalecam.
źródło
cron
jest niebezpieczny. Jeśli jedno wystąpienie crona nie zakończy się przed kolejnym terminem, prawdopodobnie będą ze sobą walczyć.Lepiej byłoby mieć ciągle działające zadanie, które usuwałoby niektóre wiersze, spało niektóre, a następnie powtarzało.
Jest również
INDEX(datetime)
bardzo ważny dla uniknięcia zakleszczeń.Ale jeśli test datetime obejmuje więcej niż, powiedzmy, 20% tabeli,
DELETE
skanuje tabelę. Mniejsze fragmenty usuwane częściej to obejście.Innym powodem wyboru mniejszych kawałków jest zablokowanie mniejszej liczby rzędów.
Dolna linia:
INDEX(datetime)
Inne techniki usuwania: http://mysql.rjweb.org/doc.php/deletebig
źródło