MySQL: czy transakcja zablokuje wiersz?

13

Nie próbowałem wcześniej używać transakcji MySQL, chcę tylko coś wyjaśnić.

Jeśli dwóch użytkowników wykona zapytanie dokładnie w tym samym czasie, jak MySQL sobie z tym poradzi? np. użytkownicy próbują zaktualizować rekord.

użytkownik1: zaktualizuj zestaw tabel kolumna = kolumna - 4 gdzie identyfikator_kolumny = 1;

użytkownik2: zaktualizuj zestaw tabel kolumna = kolumna - 7 gdzie identyfikator_kolumny = 1;

Teraz, jeśli użyję transakcji, czy MySQL wybierze, które zapytanie zostanie wykonane jako pierwsze i zablokuje drugiego użytkownika, dopóki pierwsze zapytanie nie zostanie zatwierdzone? Czy będzie to blokada stołu czy blokada rzędu?

Co się stanie, jeśli trzeci użytkownik wyda instrukcję select? Jaka będzie wartość zwracana przez MySQL?

PS to będzie na Innodb.

zer09
źródło

Odpowiedzi:

17

Jedno takie zdanie działa tak samo z MyISAM lub InnoDB, z transakcją lub z autocommit = ON. Blokuje wystarczającą liczbę zapytań, blokując w ten sposób inne połączenie. Po zakończeniu drugie połączenie będzie kontynuowane. We wszystkich przypadkach kolumna jest wkrótce zmniejszana o 11.

Trzeci użytkownik może zobaczyć wartość zmniejszoną o 0 lub 4 lub 7 lub 11. „Bardzo dokładny czas” nie jest tak naprawdę możliwy, ponieważ w pewnym momencie wykonywania każdej instrukcji sprawdzana / ustawiana jest blokada jednowątkowa . Oznacza to, że będą one serializowane, tak szybko, że nie będzie można ich zobaczyć.

InnoDB blokuje tylko wiersze, a nie tabele. (OK, instrukcja DDL robi odważniejsze blokady).

Bardziej interesująca jest transakcja, która modyfikuje dwie rzeczy lub zajmuje zauważalnie dużo czasu:

Przypadek zamiaru: Pojedynczy element, ale wymaga czasu:

BEGIN;
SELECT something;
think about it for a while
UPDATE that something;
COMMIT;

Wybierz należy zapisać w ten sposób:

SELECT something  FOR UPDATE;

Mówi to innym połączeniom: „Zamierzam zaktualizować wiersz; proszę, nie zadzieraj mi”. (Przytaczam ten przykład, ponieważ wielu początkujących tęskni za tą subtelnością).

Przypadek zakleszczenia: Bałagan z 2 rzeczami:

BEGIN;    -- in one connection
UPDATE thing_1;
UPDATE thing_2;
COMMIT;

BEGIN;    -- in another connection, at the "exact same time"
UPDATE thing_2;
UPDATE thing_1;
COMMIT;

Jest to klasyczny przykład impasu - każdy chwyta jedną rzecz, a następnie sięga po drugą. Oczywiście nie można tego zrobić. Jedna transakcja zostaje zabita; drugi kończy się. Dlatego musisz sprawdzić , czy nie ma błędów, aby je odkryć.

Normalną reakcją na impas jest odtworzenie całej nieudanej transakcji. Do tego czasu drugie połączenie nie będzie zakłócać i powinno działać bezproblemowo. (OK, kolejne połączenie może spowodować kolejny impas).

Przypadek opóźnienia: jeśli dwa połączenia chwytają wiele rzeczy w tej samej kolejności, jedno może zostać opóźnione do momentu zakończenia drugiego. Aby nie „czekać wiecznie”, domyślnie jest 50 sekund innodb_lock_wait_timeout. Twoja para prostych UPDATEsjest w rzeczywistości przykładem tego przypadku. Kończymy szybko; drugi jest zablokowany do pierwszego końca.

Zwróć uwagę, w jaki sposób Zakleszczenie (w niektórych przypadkach) można przekształcić w Opóźnienie poprzez konsekwentne porządkowanie rzeczy, których dotykasz.

autocommit = 1: Przy tym ustawieniu i bez wywoływania BEGINkażda instrukcja działa skutecznie:

BEGIN;
your statement
COMMIT;

autocommit = 0: To jest problem z oczekiwaniem na wystąpienie. Podczas wykonywania zapytania dotyczącego zapisu BEGINjest generowane niejawnie. Jednak Twoim obowiązkiem jest ostatecznie wydać COMMIT. Jeśli tego nie zrobisz, będziesz się zastanawiać, dlaczego twój system się zawiesił. (Kolejny typowy błąd dla początkujących.) Moja rada: „Nigdy nie używaj =0”.

Rick James
źródło