Przyjęte rozwiązanie jest niestety złe . To prawda, o ile mówi,
To rzeczywiście (prawie pewno; patrz poniżej), co robić. Ale wtedy sugeruje,
...a 1398 nie połączenie z zamkiem. Jak to mogło się stać? 1398 to połączenie czekające do zamka. Oznacza to, że jeszcze nie ma zamek, a zatem jego zabicie nic nie daje. Proces utrzymujący blokadę nadal będzie blokować, a następny wątek próbujący coś zrobić będzie więc również zatrzymaj się i wpisz „Oczekiwanie na blokadę metadanych” w odpowiedniej kolejności.
Nie masz gwarancji, że procesy „oczekujące na blokadę metadanych” (WFML) również się nie zablokują, ale możesz być pewien, że zabicie samych procesów WFML nie da dokładnie nic .
Prawdziwą przyczyną jest to, że inny proces wstrzymuje blokadę , a co ważniejsze, SHOW FULL PROCESSLIST
nie powie Ci bezpośrednio, który to jest .
BĘDZIE powiedzieć, czy proces działa coś, tak. Zwykle to działa. Tutaj proces trzymający blokadę nic nie robi i ukrywa się wśród innych wątków, również nic nie robiąc.
W tym przypadku winowajcą jest prawie na pewno proces 1396 , który rozpoczął się przed procesem 1398 i jest teraz w Sleep
stan i trwa od 46 sekund. Od 1396 wyraźnie robił wszystko, co musiał (o czym świadczy fakt, że teraz śpi i robił to przez 46 sekund, jeśli chodzi o MySQL ), żaden wątek, który wcześniej poszedł spać, nie mógł utrzymać blokady (lub 1396 również by się zatrzymało).
WAŻNE :jeśli łączyłeś się z MySQL jako użytkownik z ograniczeniami, SHOW FULL PROCESSLIST
nie pokaż wszystkie procesy. Więc blokada może być utrzymywana przez proces, którego nie widzisz.
Lepsza SHOW PROCESSLIST
SELECT ID, TIME, USER, HOST, DB, COMMAND, STATE, INFO
FROM INFORMATION_SCHEMA.PROCESSLIST WHERE DB IS NOT NULL
AND (`INFO` NOT LIKE '%INFORMATION_SCHEMA%' OR INFO IS NULL)
ORDER BY `DB`, `TIME` DESC
Powyższe można dostroić tak, aby pokazywały tylko procesy w stanie SLEEP, a mimo to posortuje je według czasu malejąco, więc łatwiej jest znaleźć proces, który się zawiesza (zwykle jest to Sleep
'jeden bezpośrednio przed tymi, które "czekają na blokadę metadanych").
Ważna rzecz
Pozostaw każdy proces „oczekiwania na blokadę metadanych” w spokoju .
Szybkie i brudne rozwiązanie, niezbyt zalecane, ale szybkie
Zabij wszystkich procesy w stanie „Uśpienie” w tej samej bazie danych, które są starsze niż najstarsze wątek w stanie „oczekiwanie na blokadę metadanych”. To właśnie Arnaud Amaury zrobiłby:
- dla każdej bazy danych, która ma co najmniej jeden wątek w WaitingForMetadataLock:
- Okazuje się, że najstarsze połączenie w WFML w tej bazie danych ma Z sekund
- WSZYSTKIE wątki „Sleep” w tym DB i starsze niż Z muszą zniknąć. Zacznij od najświeższych, na wszelki wypadek.
- Jeżeli w tym DB istnieje jedno starsze i nieuśpione połączenie, to może to właśnie ono trzyma blokadę, ale coś robi . Możesz oczywiście go zabić, ale zwłaszcza jeśli jest to UPDATE/INSERT/DELETE, robisz to na własne ryzyko.
Dziewięćdziesiąt dziewięć razy na sto nić do zabicia jest najmłodsza wśród osób w stanie uśpienia, które są starsze niż starszy oczekujący na blokadę metadanych:
TIME STATUS
319 Sleep
205 Sleep
19 Sleep <--- one of these two "19"
19 Sleep <--- and probably this one(*)
15 Waiting for metadata lock <--- oldest WFML
15 Waiting for metadata lock
14 Waiting for metadata lock
(*) kolejność TIME faktycznie ma milisekundy, a przynajmniej tak mi powiedziano, po prostu ich nie pokazuje. Tak więc, podczas gdy oba procesy mają wartość czasu 19, najniższy powinien być młodszy.
Bardziej ukierunkowane rozwiązanie
Uruchom SHOW ENGINE INNODB STATUS
i spójrz na sekcję „TRANSAKCJA”. Znajdziesz m.in. coś takiego
TRANSACTION 1701, ACTIVE 58 sec;2 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1
MySQL thread id 1396, OS thread handle 0x7fd06d675700, query id 1138 hostname 1.2.3.4 whatever;
Teraz możesz sprawdzić za pomocą SHOW FULL PROCESSLIST
co robi wątek id 1396 z transakcją #1701. Są szanse, że jest w stanie "Sleep". A więc:aktywna transakcja (nr 1701) z aktywną blokadą, wprowadziła nawet pewne zmiany, ponieważ ma wpis w dzienniku cofania... ale jest obecnie bezczynna. To i żaden inny nie jest wątkiem, który musisz zabić. Utrata tych zmian.
Pamiętaj, że nie robienie niczego w MySQL nie oznacza generalnie niczego. Jeśli uzyskasz jakieś rekordy z MySQL i zbudujesz plik CSV do przesyłania FTP, podczas przesyłania FTP połączenie MySQL jest bezczynne.
W rzeczywistości, jeśli proces używający MySQL i serwer MySQL znajdują się na tym samym komputerze, na tym komputerze działa Linux i masz uprawnienia roota, istnieje sposób, aby dowiedzieć się, który proces ma połączenie, które zażądało blokady. To z kolei pozwala określić (na podstawie wykorzystania procesora lub, w najgorszym przypadku, strace -ff -p pid
) czy ten proces jest naprawdę robienie czegoś lub nie, aby pomóc zdecydować, czy można bezpiecznie zabić.
Dlaczego tak się dzieje?
Widzę, że dzieje się tak w przypadku aplikacji internetowych, które używają „trwałych” lub „zgrupowanych” połączeń MySQL, co w dzisiejszych czasach zwykle oszczędza bardzo mało czasu:instancja aplikacji internetowej została zakończona, ale połączenie nie , więc jego zamek wciąż żyje... i blokuje wszystkich innych.
Kolejny interesujący sposób znalazłem, w powyższych hipotezach, aby uruchomić zapytanie zwracające niektóre wiersze, i pobrać tylko niektóre z nich . Jeśli zapytanie nie jest ustawione na „automatyczne czyszczenie” (niezależnie od tego, czy bazowy administrator DBA to robi), utrzyma połączenie i zapobiegnie przejściu pełnej blokady tabeli. Zdarzyło mi się to w kawałku kodu, który zweryfikował, czy wiersz istnieje, wybierając ten wiersz i sprawdzając, czy otrzymał błąd (nie istnieje), czy nie (musi istnieć), ale bez faktycznego pobierania wiersza .
Zapytaj DB
Inny sposób na złapanie winowajcy, jeśli masz najnowszy MySQL, ale nie za nowy ponieważ zostanie wycofane , jest (znowu potrzebujesz uprawnień w schemacie informacyjnym)
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS
WHERE LOCK_TRX_ID IN
(SELECT BLOCKING_TRX_ID FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS);
Rzeczywiste rozwiązanie, wymagające czasu i pracy
Problem jest zwykle spowodowany przez tę architekturę:
Gdy umiera aplikacja internetowa lub instancja lekkiego wątku aplikacji internetowej, pula kontenerów/połączeń może nie działać . I to jest kontener to utrzymuje połączenie otwarte, więc oczywiście połączenie się nie zamyka. Całkiem przewidywalnie, MySQL nie uważa operacji za zakończoną .
Jeśli aplikacja internetowa nie wyczyściła się po sobie (brak ROLLBACK
lub COMMIT
dla transakcji, nie UNLOCK TABLES
itp.), to cokolwiek ta aplikacja zaczęła robić jest nadal aktualne i nadal może blokować wszystkich innych.
Są więc dwa rozwiązania. Najgorsze jest obniżenie limitu czasu bezczynności
. Ale zgadnij, co się stanie, jeśli będziesz czekać zbyt długo między dwoma zapytaniami (dokładnie:„Serwer MySQL zniknął”). Możesz wtedy użyć mysql_ping
jeśli dostępne (wkrótce zostanie wycofane. Istnieją obejścia
dla ChNP. Lub możesz sprawdzić, to błąd i ponownie otwórz połączenie, jeśli tak się stanie (jest to sposób Pythona). Tak więc – za niewielką opłatą za wyniki – jest to wykonalne.
Lepsze, inteligentniejsze rozwiązanie jest mniej proste do wdrożenia. Postaraj się, aby skrypt po sobie posprzątał, zapewniając pobranie wszystkich wierszy lub zwolnienie wszystkich zasobów zapytań, przechwycenie wszystkich wyjątków i poprawną obsługę lub, jeśli to możliwe, całkowite pominięcie trwałych połączeń . Pozwól każdej instancji utworzyć własne połączenie lub użyj inteligentnego kierowca basenu
(w PHP PDO użyj PDO::ATTR_PERSISTENT
jawnie ustawione na false
). Alternatywnie (np. w PHP) możesz mieć obsługę destruct i wyjątków wymuszających wyczyszczenie połączenia przez zatwierdzanie lub wycofywanie transakcji i wydawanie jawnych odblokowań tabeli.
Nie znam sposobu zapytania o istniejące zasoby zestawu wyników w celu ich uwolnienia; jedynym sposobem byłoby zapisanie te zasoby w prywatnej tablicy.