W moich dziennikach błędów produkcyjnych czasami widzę:
SQLSTATE [HY000]: Błąd ogólny: 1205 Przekroczono limit czasu oczekiwania na blokadę; spróbuj ponownie uruchomić transakcję
Wiem, które zapytanie próbuje uzyskać dostęp do bazy danych w tym momencie, ale czy istnieje sposób, aby dowiedzieć się, które zapytanie miało blokadę w tym właśnie momencie?
Odpowiedzi:
To, co to daje, to słowo transakcja . Wyraźnie widać w stwierdzeniu, że zapytanie próbowało zmienić co najmniej jeden wiersz w co najmniej jednej tabeli InnoDB.
Ponieważ znasz zapytanie, wszystkie tabele, do których masz dostęp, są kandydatami na winowajcę.
Stamtąd powinieneś być w stanie biec
SHOW ENGINE INNODB STATUS\G
Powinieneś być w stanie zobaczyć tabele, których dotyczy problem
Otrzymujesz wszelkiego rodzaju dodatkowe informacje o blokowaniu i muteksie.
Oto próbka jednego z moich klientów:
Należy rozważyć zwiększenie wartości limitu czasu oczekiwania na blokadę dla InnoDB poprzez ustawienie innodb_lock_wait_timeout , domyślnie 50 sekund
Za
/etc/my.cnf
pomocą tego wiersza możesz ustawić wyższą wartość na stałei zrestartuj mysql. Jeśli nie możesz teraz ponownie uruchomić mysql, uruchom to:
Możesz także ustawić go na czas trwania sesji
a następnie zapytanie
źródło
innodb_lock_wait_timeout
zmienną można ustawić tylko podczas uruchamiania serwera. W przypadku wtyczki InnoDB można ją ustawić podczas uruchamiania lub zmienić w czasie wykonywania i ma ona zarówno wartości globalne, jak i sesyjne.SQL Error (1064): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\G' at line 1
SET GLOBAL innodb_lock_wait_timeout = 120;
ponownie. Jeśli/etc/my.cnf
ma opcję,innodb_lock_wait_timeout
jest ustawiony dla Ciebie. Nie każdy ma przywilej SUPER, aby zmieniać go globalnie dla wszystkich innych ( dev.mysql.com/doc/refman/5.6/en/… )Jak ktoś wspomniał w jednym z wielu wątków SO dotyczących tego problemu: Czasami proces, który zablokował tabelę, pojawia się jako spanie na liście procesów! Odrywałem włosy, aż zabiłem wszystkie śpiące wątki, które były otwarte w danej bazie danych (żadne z nich nie było wtedy aktywne). To w końcu odblokowało tabelę i pozwoliło uruchomić zapytanie o aktualizację.
Komentator powiedział coś podobnego do „Czasami wątek MySQL blokuje tabelę, a następnie śpi, czekając na coś niezwiązanego z MySQL”.
Po ponownym przejrzeniu pliku
show engine innodb status
dziennika (kiedy wyśledziłem klienta odpowiedzialnego za blokadę) zauważyłem, że zablokowany wątek był wymieniony na samym dole listy transakcji, pod aktywnymi zapytaniami, które miały się pomylić z powodu zamrożonego zamka:(niepewny, czy komunikat „Widok odczytu Trx” jest powiązany z zamrożoną blokadą, ale w przeciwieństwie do innych aktywnych transakcji, ta nie wyświetla się z wysłanym zapytaniem i zamiast tego twierdzi, że transakcja „czyści”, ale ma wiele blokady rzędu)
Morał tej historii jest taki, że transakcja może być aktywna, nawet jeśli wątek śpi.
źródło
show processlist;
aby wyświetlić wyczerpującą listę aktualnie wykonywanych procesów, co jest miłe, ponieważ jest to skrócona wersjashow engine innodb status\g
. Ponadto, jeśli twoja baza danych znajduje się w instancji Amazon RDS, możesz użyćCALL mysql.rds_kill(<thread_id>);
do zabicia wątków. Myślę, że ma wyższe uprawnienia, ponieważ pozwoliło mi zabić więcej procesów niż zwykłykill <thread_id>;
- zwróć uwagę, że powinny one być uruchamiane w MySQL CLIZe względu na popularność MySQL nic dziwnego, że przekroczono limit czasu oczekiwania na blokadę; spróbuj ponownie uruchomić transakcję wyjątek zyska tyle uwagi na SO.
Im więcej masz rywalizacji, tym większa szansa na zakleszczenie, które silnik DB rozwiąże przez przekroczenie limitu czasu jednej z zakleszczonych transakcji. Ponadto długotrwałe transakcje, które zmodyfikowały (np.
UPDATE
LubDELETE
) dużą liczbę pozycji (które blokują się, aby uniknąć nieprawidłowych zapisów, jak wyjaśniono w sekcji Wytrzymałość Java o wysokiej wydajności książce ), są bardziej narażone na konflikty z innymi transakcjami.Chociaż InnoDB MVCC, nadal możesz zażądać jawnych blokad za pomocą
FOR UPDATE
klauzuli . Jednak w przeciwieństwie do innych popularnych baz danych (Oracle, MSSQL, PostgreSQL, DB2), MySQL używaREPEATABLE_READ
jako domyślnego poziomu izolacji .Teraz blokady, które uzyskałeś (modyfikując wiersze lub używając jawnego blokowania), są wstrzymywane na czas trwania bieżącej transakcji. Jeśli chcesz dobre wyjaśnienie różnicy między
REPEATABLE_READ
iREAD COMMITTED
w odniesieniu do blokowania, należy przeczytać ten artykuł Percona .Dlatego: Im bardziej restrykcyjny poziom izolacji (
REPEATABLE_READ
,SERIALIZABLE
), tym większa szansa na zakleszczenie. To nie jest problem „per se”, to kompromis.Możesz uzyskać bardzo dobre wyniki
READ_COMMITED
, ponieważ potrzebujesz zapobiegania utracie aktualizacji na poziomie aplikacji, gdy używasz transakcji logicznych obejmujących wiele żądań HTTP. Te optymistyczne zamek cele podejście utracone aktualizacje , które mogą się zdarzyć nawet jeśli używaszSERIALIZABLE
poziom izolacji przy jednoczesnym zmniejszeniu twierdzenie blokady przez co pozwala na użycieREAD_COMMITED
.źródło
Dla przypomnienia wyjątek limitu czasu oczekiwania na blokadę występuje również, gdy występuje zakleszczenie i MySQL nie może go wykryć, więc po prostu upłynął limit czasu. Innym powodem może być bardzo długo działające zapytanie, które jednak jest łatwiejsze do rozwiązania / naprawy i nie będę tutaj opisywał tego przypadku.
MySQL jest zazwyczaj w stanie poradzić sobie z impasami, jeśli są one skonstruowane „prawidłowo” w ramach dwóch transakcji. MySQL następnie zabija / przywraca jedną transakcję, która ma mniej blokad (jest mniej ważna, ponieważ wpłynie na mniej wierszy) i pozwala drugiej zakończyć.
Załóżmy teraz, że istnieją dwa procesy A i B oraz 3 transakcje:
Jest to bardzo niefortunna konfiguracja, ponieważ MySQL nie widzi zakleszczenia (obejmuje 3 transakcje). Więc to, co robi MySQL, to ... nic! Po prostu czeka, bo nie wie, co robić. Czeka, aż pierwsza uzyskana blokada przekroczy limit czasu (Proces A Transakcja 1: Blokuje X), a następnie odblokuje Blokadę X, która odblokuje Transakcję 2 itd.
Sztuka polega na ustaleniu, co (które zapytanie) powoduje pierwszą blokadę (Blokada X). Będziesz mógł łatwo zobaczyć (
show engine innodb status
), że Transakcja 3 czeka na Transakcję 2, ale nie zobaczysz, na którą Transakcja 2 czeka (Transakcja 1). MySQL nie wydrukuje żadnych blokad ani zapytań związanych z Transakcją 1. Jedyną wskazówką będzie to, że na samym dole listy transakcji (show engine innodb status
wydruku) zobaczysz Transakcję 1 najwyraźniej nic nie robiąc (ale w rzeczywistości czeka na Transakcję 3, aby koniec).Technika określania, które zapytanie SQL powoduje przyznanie blokady (Lock X) dla danej transakcji, która czeka, została opisana tutaj
Tracking MySQL query history in long running transactions
Jeśli zastanawiasz się, na czym dokładnie polega proces i transakcja. Proces jest procesem PHP. Transakcja jest transakcją zdefiniowaną przez innodb-trx-table . W moim przypadku miałem dwa procesy PHP, w każdym rozpocząłem transakcję ręcznie. Co ciekawe, mimo że rozpocząłem jedną transakcję w procesie, MySQL użył w rzeczywistości dwóch osobnych transakcji (nie mam pojęcia, dlaczego, może niektórzy deweloperzy MySQL mogą to wyjaśnić).
MySQL zarządza wewnętrznie własnymi transakcjami i zdecydował (w moim przypadku), aby użyć dwóch transakcji do obsługi wszystkich żądań SQL pochodzących z procesu PHP (Proces A). Stwierdzenie, że Transakcja 1 czeka na zakończenie Transakcji 3, jest wewnętrzną sprawą MySQL. MySQL „wiedział”, że transakcja 1 i transakcja 3 zostały faktycznie utworzone w ramach jednego żądania „transakcji” (z procesu A). Teraz cała „transakcja” została zablokowana, ponieważ transakcja 3 (podsekcja „transakcji”) została zablokowana. Ponieważ „transakcja” nie była w stanie zakończyć Transakcji 1 (również podsekcja „transakcji”) została również oznaczona jako niezakończona. To mam na myśli przez „Transakcja 1 czeka na zakończenie transakcji 3”.
źródło
Dużym problemem związanym z tym wyjątkiem jest to, że zwykle nie jest on odtwarzalny w środowisku testowym i nie jesteśmy w pobliżu, aby uruchomić status silnika innodb, gdy dzieje się to na prod. Dlatego w jednym z projektów umieściłem poniższy kod w bloku catch dla tego wyjątku. To pomogło mi złapać status silnika, kiedy nastąpił wyjątek. To bardzo pomogło.
źródło
Spójrz na stronę podręcznika
pt-deadlock-logger
narzędzia :Wydobywa informacje ze
engine innodb status
wspomnianego powyżej, a także można go użyć do utworzenia,daemon
który działa co 30 sekund.źródło
Ekstrapolując z powyższej odpowiedzi Rolando, to one blokują twoje zapytanie:
Jeśli musisz wykonać zapytanie i nie możesz czekać na uruchomienie innych, zabij ich za pomocą identyfikatora wątku MySQL:
(oczywiście z poziomu mysql, a nie powłoki, oczywiście)
Musisz znaleźć identyfikatory wątku z:
polecenie i dowiedz się, który z nich blokuje bazę danych.
źródło
5341773
? Nie rozumiem, co odróżnia tę jedną od innych.Oto, co ostatecznie musiałem zrobić, aby dowiedzieć się, co „inne zapytanie” spowodowało problem z przekroczeniem limitu czasu blokady. W kodzie aplikacji śledzimy wszystkie oczekujące wywołania bazy danych w osobnym wątku poświęconym temu zadaniu. Jeśli jakiekolwiek wywołanie DB trwa dłużej niż N sekund (dla nas to 30 sekund), logujemy:
Dzięki powyższym byliśmy w stanie wskazać współbieżne zapytania blokujące wiersze powodujące impas. W moim przypadku były to stwierdzenia podobne
INSERT ... SELECT
które w przeciwieństwie do zwykłych instrukcji SELECT blokują leżące poniżej wiersze. Następnie możesz zreorganizować kod lub użyć innej izolacji transakcji, np. Odczytać niezaangażowane.Powodzenia!
źródło
Możesz użyć:
która wyświetli listę wszystkich połączeń w MySQL i bieżący stan połączenia, a także wykonywane zapytanie. Istnieje również krótszy wariant,
show processlist;
który wyświetla skrócone zapytanie oraz statystyki połączenia.źródło
Jeśli używasz JDBC, masz opcję
includeInnodbStatusInDeadlockExceptions = true
https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-configuration-properties.html
źródło
Aktywuj MySQL general.log (wymagający dużej ilości dysku) i użyj mysql_analyse_general_log.pl, aby wyodrębnić długoterminowe transakcje, na przykład:
Następnie wyłącz general.log.
źródło