2016-12-07 15 views
21

Używam MySQL 5.5. Zauważyłem osobliwy zakleszczenie występujące w równoległym scenariuszu i nie sądzę, aby wystąpił ten impas.Unikanie zakleszczenia MySQL podczas aktualizacji udostępnianej do wyłącznej blokady

odtworzyć w ten sposób, za pomocą dwóch mysql sesje klienckie uruchomione jednocześnie:

sesji mysql 1:

create table parent (id int(11) primary key); 
insert into parent values (1); 
create table child (id int(11) primary key, parent_id int(11), foreign key (parent_id) references parent(id)); 

begin; 
insert into child (id, parent_id) values (10, 1); 
-- this will create shared lock on parent(1) 

sesji mysql 2:

begin; 
-- try and get exclusive lock on parent row 
select id from parent where id = 1 for update; 
-- this will block because of shared lock in session 1 

mysql Sesja 1:

-- try and get exclusive lock on parent row 
select id from parent where id = 1 for update; 
-- observe that mysql session 2 transaction has been rolled back 

mysql sesja 2:

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction 

Przekazywane z show engine innodb status to:

------------------------ 
LATEST DETECTED DEADLOCK 
------------------------ 
161207 10:48:56 
*** (1) TRANSACTION: 
TRANSACTION 107E67, ACTIVE 43 sec starting index read 
mysql tables in use 1, locked 1 
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s) 
MySQL thread id 13074, OS thread handle 0x7f68eccfe700, query id 5530424 localhost root statistics 
select id from parent where id = 1 for update 
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 0 page no 3714 n bits 72 index `PRIMARY` of table `foo`.`parent` trx id 107E67 lock_mode X locks rec but not gap waiting 
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 
0: len 4; hex 80000001; asc  ;; 
1: len 6; hex 000000107e65; asc  ~e;; 
2: len 7; hex 86000001320110; asc  2 ;; 

*** (2) TRANSACTION: 
TRANSACTION 107E66, ACTIVE 52 sec starting index read 
mysql tables in use 1, locked 1 
5 lock struct(s), heap size 1248, 2 row lock(s), undo log entries 1 
MySQL thread id 12411, OS thread handle 0x7f68ecfac700, query id 5530425 localhost root statistics 
select id from parent where id = 1 for update 
*** (2) HOLDS THE LOCK(S): 
RECORD LOCKS space id 0 page no 3714 n bits 72 index `PRIMARY` of table `foo`.`parent` trx id 107E66 lock mode S locks rec but not gap 
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 
0: len 4; hex 80000001; asc  ;; 
1: len 6; hex 000000107e65; asc  ~e;; 
2: len 7; hex 86000001320110; asc  2 ;; 

*** (2) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 0 page no 3714 n bits 72 index `PRIMARY` of table `foo`.`parent` trx id 107E66 lock_mode X locks rec but not gap waiting 
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 
0: len 4; hex 80000001; asc  ;; 
1: len 6; hex 000000107e65; asc  ~e;; 
2: len 7; hex 86000001320110; asc  2 ;; 

*** WE ROLL BACK TRANSACTION (1) 

Można zobaczyć, że transakcję (1) nie wykazują żadnych S lub X zamki już nabyte; jest po prostu zablokowany, próbując zdobyć ekskluzywny zamek. Ponieważ nie ma cyklu, nie powinno być impasu w tej sytuacji, jak rozumiem.

Czy jest to znany błąd MySQL? Czy inni ludzie to zauważyli? Jakie obejścia zastosowano?

Są możliwe kroki naprzód mogliśmy zabrać:

  • zmniejszyć użycie kluczy obcych (w naszym scenariuszu produkcji, tylko miękki usuwanie wierszy w wymienionej tabeli, ale jest icky)
  • Acquire wyłączne blokowanie z góry zamiast ukrytych udostępnionych blokad (zmniejszy naszą równoczesną przepustowość)
  • Zmień naszą logikę, abyśmy już nie potrzebowali wyłącznej blokady dla rodzica w tej samej transakcji, która dodaje wiersz dziecka (ryzykowny i trudny).
  • Zmień naszą wersję od MySQL do o ne, które nie wykazuje tego zachowania.

Czy są inne opcje, których nie rozważamy?

+1

https://bugs.mysql.com/bug.php?id=48652 Szczególnie 22 października 2012 12:32 Komentarz Marko Mäkelä. – bishop

+0

Właśnie powielono powyższe kroki. Nie ma błędu w Mysql 5.1.73 UPD. Ale błąd istnieje w 5.7.17, więc uważam, że jest to wersja> 5.5 specyficzne zachowanie –

Odpowiedz

5

To wieloletnie błąd, który można przeczytać więcej z: This bug report

Jest to problem w MySQL poziom blokowania tabeli.

Wewnętrznie w obrębie InnoDB, sprawdzanie ograniczenia klucza OBCEGO może odczytywać (lub, z opcją ON UPDATE lub ON DELETE, pisać) tabele rodzica lub podrzędne.

Normalnie dostęp tabela jest regulowane przez następujące zamki: 1. MySQL meta-dane zablokować tabela 2. InnoDB zablokować 3.Zamki do zapisu InnoDB

Wszystkie te zamki są przechowywane do końca transakcji.

Tablica InnoDB i blokady rekordów są pomijane w niektórych trybach, ale nie podczas sprawdzania kluczy obcych. Zakleszczenie jest spowodowane tym, że MySQL nabywa blokadę meta-danych tylko dla tabel, które jawnie są wymienione w instrukcjach SQL.

Sądzę, że obejście problemu może polegać na uzyskaniu dostępu do tabel potomnych (lub rodzica) na początku transakcji, przed problematyczną operacją KEY ENDEIGN .

Czytaj dyskusję i to odpowiadać za

2

Powodem aktualizacji wiersz nadrzędny nie został podany, ale Przypuszczam, że ma to związek z jakimś de normalizacji, na podstawie tej sekwencji z pytaniem:

-- session 1 
begin; 
insert into child (id, parent_id) values (10, 1); 
... 
select id from parent where id = 1 for update; 

na przykład, rozkaz (tabela rodzic) ma wysokość kolumny, który utrzymuje się jako sumę ilości wszystkich linii rzędu (dzieci tabeli).

Wydaje się logiczne, aby utrzymać dane nadrzędną jest kodowana w aplikacji sama (z wypowiedzi wyraźny update), który ma następujące konsekwencje:

  • Jeśli wkładka do dziecka odbywa się w wielu różnych miejscach , to logika aplikacji w kliencie musi być aktualizowana we wszystkich tych miejscach, aby zachować integralność. To jest powielanie kodu.

  • Nawet jeśli odbywa się to tylko w jednym miejscu, fakt, że tabela nadrzędna musi zostać zaktualizowana, gdy dodanie dziecka nie jest możliwe, aby serwer mógł się dowiedzieć.

Zamiast tego, należy rozważyć następujące opcje:

Definiowanie wyzwalaczy na tabeli podrzędnej, że aktualizacja tabeli nadrzędnej, ile potrzeba.

Posiada następujące konsekwencje:

  • pierwszy, logikę, aby utrzymać tabeli nadrzędnej nie jest już (ewentualnie) powielane, jak to jest w samym spuście.

  • Po drugie, i to jest ważna część tutaj, serwer MySQL teraz wie, że tabeli nadrzędnej jest aktualizowany, gdy rekord dziecko jest włożona, a ponieważ tego, właściwa blokada (wyłączny zamiast dzielone) jest wzięty.

Testowane z wersją 8.0, patrz poniżej.

odniesieniu do troski o przepustowości współbieżności,

  • różne transakcje działające na różnych wierszach dominujących wykona w równolegle, jako wyłączne zamki są podejmowane na dominującej (różne) wierszy, nie tabelę nadrzędną.

  • transakcje działające jednocześnie w tym samym wierszu dominującą rzeczywiście być szeregowane ... co jest rzeczywiście oczekiwany wynik, ponieważ wypełnić na samo rekord w każdym razie.

Szeregowania transakcje, które są gwarantowane, aby odnieść sukces należy zapewnić lepszą wydajność (o ile obciążenie zgłoszenie dotyczy), że posiadanie jakąś transakcję nie, tylko je ponowić.

Oczywiście, należy również zaktualizować i usunąć wyzwalacze, aby zaktualizować także nadrzędnego, w zależności od logiki aplikacji.

konfiguracji

create table parent (
    id int(11) primary key, 
    number_of_children int(11)); 

create table child (
    id int(11) primary key, 
    parent_id int(11), 
    foreign key (parent_id) references parent(id)); 

delimiter $$; 
create trigger bi_child before insert on child 
for each row 
begin 
    update parent 
    set number_of_children = number_of_children + 1 
    where id = NEW.parent_id; 
end 
$$ 
delimiter ;$$ 

begin; 
insert into parent values (1, 0); 
insert into parent values (2, 0); 
commit; 

sesji 1

begin; 
insert into child values (10, 1); 

sesji 2

begin; 
insert into child values (20, 2); 

nie jest zablokowany, jak tarasowy t rodzic jest używany.

sesji 3

begin; 
-- this now blocks, waiting for an X lock on parent row 1. 
insert into child values (11, 1); 

sesji 1

-- unlocks session 3 
commit; 

sesji 3

popełnienia;

sesji 2

popełnienia;

Wyniki

select * from parent; 
id  number_of_children 
1  2 
2  1 
+0

Nie, to nie jest denormalizacja, a logika nie może być zaimplementowana jako wyzwalacz. Stan na rodzica zasadniczo wskazuje, że odbywa się przetwarzanie w tle. Asynchronicznie, wiersze są dodawane do innej tabeli, która odwołuje się do wiersza nadrzędnego. Ale nie rozpoczynamy pracy asynchronicznej, jeśli wiersz nadrzędny jest już w stanie.Używamy wyłącznego blokowania w wierszu nadrzędnym, aby kontrolować synchronizację z zadaniem w tle. Dziwne jest to, że to, co powinno być bezpieczne, zamienia się w wykryte zakleszczenia. –

+0

@BarryKelly, Przepraszamy, że wyzwalacze nie działają w Twoim przypadku. To ograniczenie nie było udokumentowane w pytaniu ... Jeśli zapytasz "Czy są inne opcje, których nie rozważamy?", Myślę, że byłoby sprawiedliwie wyjaśnić, co próbujesz osiągnąć. –

+0

Wiem. Przepraszam. Zwolniłem go do minimalnego zestawu powtarzalnych kroków. W ten sposób przekroczyłem moje wymagania dotyczące aplikacji. Ale gdybym opisał moje wymagania dotyczące aplikacji, wchodzilibyśmy w znacznie szerszą dyskusję o szerszym zakresie. –

Powiązane problemy