Wyszło to z tego pokrewnego pytania , w którym chciałem wiedzieć, jak zmusić dwie transakcje do sekwencyjnego wykonywania w trywialnym przypadku (gdzie obie operują tylko w jednym rzędzie). Otrzymałem odpowiedź - użyj SELECT ... FOR UPDATE
jako pierwszego wiersza obu transakcji - ale prowadzi to do problemu: jeśli pierwsza transakcja nigdy nie zostanie zatwierdzona lub wycofana, druga transakcja zostanie zablokowana na czas nieokreślony. innodb_lock_wait_timeout
Zmienna określa liczbę sekund, po którym klient próbuje zrobić drugą transakcję zostanie powiedziane „Niestety, spróbuj ponownie” ... ale o ile mogę powiedzieć, że będą ponownie próbują aż do następnego uruchomienia serwera. Więc:
- Z pewnością musi istnieć sposób na wymuszenie,
ROLLBACK
jeśli transakcja trwa wiecznie? Czy muszę uciekać się do używania demona do zabijania takich transakcji, a jeśli tak, to jak taki demon wyglądałby? - Jeśli połączenie zostanie przerwane w trakcie
wait_timeout
lub w trakcieinteractive_timeout
transakcji, czy transakcja zostanie wycofana? Czy istnieje sposób na przetestowanie tego z poziomu konsoli?
Wyjaśnienie : innodb_lock_wait_timeout
ustawia liczbę sekund, przez które transakcja będzie czekać na zwolnienie blokady przed poddaniem się; chcę wymusić zwolnienie blokady.
Aktualizacja 1 : Oto prosty przykład, który pokazuje, dlaczego innodb_lock_wait_timeout
nie wystarczy, aby pierwsza transakcja nie została zablokowana przez pierwszą:
START TRANSACTION;
SELECT SLEEP(55);
COMMIT;
Przy ustawieniu domyślnym innodb_lock_wait_timeout = 50
ta transakcja kończy się bez błędów po 55 sekundach. A jeśli dodasz znak UPDATE
przed SLEEP
wierszem, a następnie zainicjujesz drugą transakcję od innego klienta, który próbuje przejść do SELECT ... FOR UPDATE
tego samego wiersza, nastąpi przekroczenie limitu czasu drugiej transakcji, a nie tej, która zasnęła.
To, czego szukam, to sposób na wymuszenie końca spokojnego snu tej transakcji.
Aktualizacja 2 : W odpowiedzi na obawy hobodave dotyczące tego, jak realistyczny jest powyższy przykład, oto alternatywny scenariusz: DBA łączy się z serwerem na żywo i działa
START TRANSACTION
SELECT ... FOR UPDATE
gdzie druga linia blokuje wiersz, do którego aplikacja często zapisuje. Następnie DBA zostaje przerwane i odchodzi, zapominając o zakończeniu transakcji. Aplikacja zgrzyta, dopóki wiersz nie zostanie odblokowany. Chciałbym zminimalizować czas, w którym aplikacja utknęła w wyniku tego błędu.
ROLLBACK
pierwszą transakcję, jeśli jej wykonanie zajmuje więcej niżn
sekundy. Czy jest na to jakiś sposób?MYSQL
nie mam konfiguracji, która zapobiegałaby temu scenariuszowi. Ponieważ nie można zaakceptować zawieszenia serwera z powodu nieodpowiedzialności klientów. Nie miałem trudności ze zrozumieniem twojego pytania, jest ono również bardzo istotne.Odpowiedzi:
Ponad połowa tego wątku wydaje się dotyczyć zadawania pytań w usłudze ServerFault. Myślę, że pytanie ma sens i jest dość proste: Jak automatycznie wycofać wstrzymaną transakcję?
Jednym z rozwiązań, jeśli chcesz zabić całe połączenie, jest ustawienie wait_timeout / interactive_timeout. Zobacz /programming/9936699/mysql-rollback-on-transaction-with-lost-disconnected-connection .
źródło
Ponieważ pytanie jest zadawane tutaj w usłudze ServerFault, logiczne jest założenie, że szukasz rozwiązania MySQL dla problemu MySQL, szczególnie w dziedzinie wiedzy, w której administrator systemu i / lub DBA mieliby wiedzę specjalistyczną. sekcja dotyczy twoich pytań:
Nie zrobi tego. Myślę, że nie rozumiesz
innodb_lock_wait_timeout
. Robi dokładnie to, czego potrzebujesz.Wróci z błędem, jak podano w instrukcji:
innodb_lock_wait_timeout
kilka sekund.Domyślnie transakcja nie zostanie wycofana. Twój kod aplikacji jest odpowiedzialny za podjęcie decyzji o tym, jak poradzić sobie z tym błędem, bez względu na to, czy spróbujesz ponownie, czy wycofać.
Jeśli chcesz automatycznie wycofać, wyjaśniono to również w instrukcji:
RE: Twoje liczne aktualizacje i komentarze
Po pierwsze, w swoich komentarzach stwierdziłeś, że „chciałeś” powiedzieć, że chcesz przekroczyć limit czasu pierwszej transakcji, która blokuje się na czas nieokreślony. Nie wynika to z pierwotnego pytania i jest sprzeczne z „Jeśli pierwsza transakcja nigdy nie zostanie zatwierdzona lub wycofana, wówczas druga transakcja zostanie zablokowana na czas nieokreślony”.
Niemniej jednak mogę również odpowiedzieć na to pytanie. Protokół MySQL nie ma „limitu czasu zapytania”. Oznacza to, że nie można przekroczyć limitu czasu pierwszej zablokowanej transakcji. Musisz poczekać, aż się zakończy, lub zabić sesję. Po zakończeniu sesji serwer automatycznie wycofuje transakcję.
Jedyną inną alternatywą byłoby użycie lub napisanie biblioteki mysql, która wykorzystuje nieblokujące wejścia / wyjścia, co pozwoliłoby twojej aplikacji na zabicie wątku / rozwidlenia wykonującego zapytanie po N sekundach. Implementacja i użycie takiej biblioteki wykracza poza zakres ServerFault. To jest właściwe pytanie dla StackOverflow .
Po drugie, w komentarzach podałeś następujące informacje:
Nie było to wcale widoczne w twoim pierwotnym pytaniu i nadal tak nie jest. Można to rozpoznać dopiero po udostępnieniu tego dość ważnego smakołyka w komentarzu.
Jeśli to jest problem, który próbujesz rozwiązać, obawiam się, że zadałeś go na niewłaściwym forum. Opisałeś problem programowania na poziomie aplikacji, który wymaga rozwiązania programistycznego, którego MySQL nie jest w stanie zapewnić i jest poza zakresem tej społeczności. Twoja ostatnia odpowiedź odpowiada na pytanie „Jak zapobiec nieskończonemu zapętleniu się programu Ruby?”. To pytanie jest nie na temat dla tej społeczności i powinno być zadawane na StackOverflow .
źródło
innodb_lock_wait_timeout
sugerują), nie jest tak w przedstawionej przeze mnie sytuacji: gdzie klient zawiesza się lub ulega awarii, zanim otrzyma zmianę, aby wykonaćCOMMIT
lubROLLBACK
.COMMIT
lubROLLBACK
komunikat zostanie rozstrzygnięty? Hobodave, być może byłoby pomocne, gdybyś przedstawił swój kod testowy.W podobnej sytuacji mój zespół zdecydował się na użycie pt-kill ( https://www.percona.com/doc/percona-toolkit/2.1/pt-kill.html ). Może raportować lub zabijać zapytania, które (między innymi) działają zbyt długo. Można wtedy uruchomić go w cronie co X minut.
Problem polegał na tym, że zapytanie, które miało nieoczekiwanie długi (np. Nieokreślony) czas wykonania, zablokowało procedurę tworzenia kopii zapasowej, która próbowała opróżnić tabele blokadą odczytu, co z kolei zablokowało wszystkie inne zapytania „oczekiwania na opróżnienie tabel”, które nigdy nie przekroczyły limitu czasu ponieważ nie czekają na blokadę, co spowodowało brak błędów w aplikacji, co ostatecznie spowodowało brak alertów dla administratorów i zatrzymanie aplikacji.
Poprawka polegała oczywiście na naprawieniu początkowego zapytania, ale aby uniknąć sytuacji, która przypadkowo zdarzy się w przyszłości, byłoby świetnie, gdyby możliwe było automatyczne zabijanie (lub zgłaszanie) transakcji / zapytań trwających dłużej niż określony czas, który autor pytania wydaje się zadawać. Jest to możliwe w przypadku pt-kill.
źródło
Prostym rozwiązaniem będzie posiadanie procedury składowanej w celu zabicia zapytań, które zajmują więcej czasu niż wymagany
timeout
czas i użycieinnodb_rollback_on_timeout
opcji wraz z nim.źródło
Po Googlingu przez jakiś czas wygląda na to, że nie ma bezpośredniego sposobu na ustawienie limitu czasu na transakcję. Oto najlepsze rozwiązanie, jakie udało mi się znaleźć:
Komenda
daje tabelę z wszystkimi aktualnie wykonywanymi poleceniami i czasem (w sekundach) od ich uruchomienia. Gdy klient przestaje wydawać polecenia, opisuje się je jako wydawanie
Sleep
polecenia, przy czymTime
kolumna jest liczbą sekund od zakończenia ostatniego polecenia. Możesz więc ręcznie wejść iKILL
wszystko, co maTime
wartość powyżej, powiedzmy, 5 sekund, jeśli masz pewność, że żadne zapytanie nie powinno zająć więcej niż 5 sekund w bazie danych. Na przykład, jeśli proces o id 3 ma wTime
kolumnie wartość 12 , możesz to zrobićDokumentacja składni KILL sugeruje, że transakcja przeprowadzana przez wątek o tym identyfikatorze zostałaby wycofana (chociaż tego nie testowałem, więc bądź ostrożny).
Ale jak to zautomatyzować i zabijać wszystkie skrypty w godzinach nadliczbowych co 5 sekund? Pierwszy komentarz na tej samej stronie daje nam podpowiedź, ale kod jest w PHP; wydaje się, że nie możemy zrobić
SELECT
naSHOW PROCESSLIST
z poziomu klienta MySQL. Załóżmy jednak, że mamy demona PHP, który uruchamiamy co$MAX_TIME
sekundę, może wyglądać mniej więcej tak:Zauważ, że spowodowałoby to efekt uboczny, zmuszając wszystkie bezczynne połączenia klientów do ponownego połączenia, nie tylko te, które źle się zachowują.
Jeśli ktoś ma lepszą odpowiedź, chciałbym ją usłyszeć.
Edycja: Istnieje co najmniej jedno poważne ryzyko związane z użyciem
KILL
w ten sposób. Załóżmy, że używasz powyższego skryptu doKILL
każdego połączenia, które jest bezczynne przez 10 sekund. Po 10 sekundach bezczynności połączenia, ale przed jegoKILL
wydaniem użytkownik, którego połączenie jest zabijane, wydajeSTART TRANSACTION
polecenie. Następnie sąKILL
edytowane. Wysyłają,UPDATE
a następnie, zastanawiając się dwa razy, wydająROLLBACK
. Ponieważ jednakKILL
wycofano pierwotną transakcję, aktualizacja przeszła natychmiast i nie można jej przywrócić! Zobacz ten post dla przypadku testowego.źródło
Jak sugeruje hobodave w komentarzu, u niektórych klientów (choć najwyraźniej nie jest to
mysql
narzędzie wiersza poleceń) możliwe jest ustawienie limitu czasu dla transakcji. Oto demonstracja użycia ActiveRecord w Ruby:W tym przykładzie transakcja wygasa po 5 sekundach i zostaje automatycznie wycofana . ( Aktualizacja w odpowiedzi na komentarz hobodave'a: jeśli odpowiedź bazy danych zajmie więcej niż 5 sekund, transakcja zostanie wycofana tak szybko, jak to nastąpi - nie wcześniej). Jeśli chcesz mieć pewność, że wszystkie transakcje przekroczą limit czasu po
n
sekundach, możesz zbudować opakowanie wokół ActiveRecord. Zgaduję, że dotyczy to również najpopularniejszych bibliotek Java, .NET, Python itp., Ale jeszcze ich nie testowałem. (Jeśli tak, napisz komentarz do tej odpowiedzi.)Transakcje w ActiveRecord mają również tę zaletę, że są bezpieczne, jeśli
KILL
zostanie wydane, w przeciwieństwie do transakcji wykonanych z wiersza poleceń. Zobacz /dba/1561/mysql-client-believes-theyre-in-a-transaction-gets-killed-wreaks-havoc .Wydaje się, że nie jest możliwe wymuszenie maksymalnego czasu transakcji po stronie serwera, z wyjątkiem skryptu takiego jak ten, który zamieściłem w drugiej odpowiedzi.
źródło
Foo.create
polecenie zostanie zablokowane na 5 minut, Limit czasu go nie zabije. Jest to niezgodne z przykładem podanym w pytaniu. ASELECT ... FOR UPDATE
może łatwo blokować przez długi czas, tak jak każde inne zapytanie.ActiveRecord::Base#find_by_sql
: gist.github.com/856100 Ponownie, to dlatego, że AR używa blokujących I / O.timeout
metoda cofnie transakcję, ale nie dopóki baza danych MySQL nie odpowie na zapytanie. Tak więctimeout
metoda jest odpowiednia do zapobiegania długim transakcjom po stronie klienta lub długim transakcjom, które składają się z kilku stosunkowo krótkich zapytań, ale nie w przypadku długich transakcji, w których winowajcą jest pojedyncze zapytanie.