2008-11-06 12 views
8

Chcę, aby moja biblioteka Pythona pracująca z MySQLdb była w stanie wykryć zakleszczenia i spróbować ponownie. Uważam, że zakodowałem dobre rozwiązanie, a teraz chcę je przetestować.Jak mogę spowodować zakleszczenie w MySQL dla celów testowych?

Jakieś pomysły na najprostsze zapytania, które mógłbym uruchomić przy użyciu MySQLdb, aby stworzyć stan zakleszczenia?

Info System:

  • MySQL 5.0.19
  • Client 5.1.11
  • Windows XP
  • Python 2.4/MySQLdb 1.2.1 p2

Odpowiedz

1

zawsze można uruchomić LOCK TABLE nazwa tabeli z innej sesji (np. Mysql CLI). To może załatwić sprawę.

Zostanie zablokowany do momentu zwolnienia lub rozłączenia sesji.

+0

To spowodowałoby po prostu błąd OperationalError: (1205, Przekroczono limit czasu oczekiwania na blokadę, spróbuj ponownie uruchomić transakcję "), nie? – Greg

+0

@Greg: Myślę, że mówią o jednej sesji robiącej LOCK TABLE A, i kolejnej sesji robią LOCK TABLE B. W tej chwili muszą się zsynchronizować. Następnie sesja pierwsza próbuje TABELA BLOKÓW B. Gdy dwie próby sesji LOCK TABLE A - to zakleszczenie. –

1

Nie jestem fanem Pythona, więc wybacz mój niepoprawny język Jeśli mówię to źle ... ale otwórz dwie sesje (w osobnych oknach lub z oddzielnych procesów Pythona - z oddzielnych skrzynek zadziała ...) Następnie ...

. W sesji A:

Begin Transaction 
     Insert TableA() Values()... 

. Następnie w sesji B:

Begin Transaction 
    Insert TableB() Values()... 
    Insert TableA() Values() ... 

. Następnie wróć do sesji

Insert TableB() Values() ... 

Dostaniesz impasu ...

+0

Jak mogę się upewnić, że działają dokładnie w tym samym czasie? – Greg

+0

Przepraszam, miałem na celu "ręcznie" kontrolować, kiedy każdy z trzech powyższych kroków jest wykonywany ... Najpierw rozpocznij transakcję i wstaw tabe a w sesji A, a następnie przejdź do drugiego procesu i wykonaj drugi fragment, a następnie przejdź do pierwszego proces i (może mieć przycisk użytkownika, aby to zrobić) wykonaj ostatni fragment –

+0

Bez używania Pythona lub jakiegokolwiek innego kodu klienta, po prostu piszę instrukcje Insert w dwóch oknach SQL i przechodzę od jednego do drugiego. Ale chcesz, żeby twój kod "uwięził" i wykrył zakleszczenie ... –

1

Chcesz coś wzdłuż następujących linii.

parent.py

import subprocess 
c1= subprocess.Popen(["python", "child.py", "1"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 
c2= subprocess.Popen(["python", "child.py", "2"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 
out1, err1= c1.communicate("to 1: hit it!") 
print " 1:", repr(out1) 
print "*1:", repr(err1) 
out2, err2= c2.communicate("to 2: ready, set, go!") 
print " 2:", repr(out2) 
print "*2:", repr(err2) 
out1, err1= c1.communicate() 
print " 1:", repr(out1) 
print "*1:", repr(err1) 
out2, err2= c2.communicate() 
print " 2:", repr(out2) 
print "*2:", repr(err2) 
c1.wait() 
c2.wait() 

child.py

import yourDBconnection as dbapi2 

def child1(): 
    print "Child 1 start" 
    conn= dbapi2.connect(...) 
    c1= conn.cursor() 
    conn.begin() # turn off autocommit, start a transaction 
    ra= c1.execute("UPDATE A SET AC1='Achgd' WHERE AC1='AC1-1'") 
    print ra 
    print "Child1", raw_input() 
    rb= c1.execute("UPDATE B SET BC1='Bchgd' WHERE BC1='BC1-1'") 
    print rb 
    c1.close() 
    print "Child 1 finished" 

def child2(): 
    print "Child 2 start" 
    conn= dbapi2.connect(...) 
    c1= conn.cursor() 
    conn.begin() # turn off autocommit, start a transaction 
    rb= c1.execute("UPDATE B SET BC1='Bchgd' WHERE BC1='BC1-1'") 
    print rb 
    print "Child2", raw_input() 
    ra= c1.execute("UPDATE A SET AC1='Achgd' WHERE AC1='AC1-1'") 
    print ta 
    c1.close() 
    print "Child 2 finish" 

try: 
    if sys.argv[1] == "1": 
     child1() 
    else: 
     child2() 
except Exception, e: 
    print repr(e) 

Uwaga symetria. Każde dziecko zaczyna od posiadania jednego zasobu. Następnie próbują zdobyć zasoby należące do kogoś innego. Możesz dla zabawy mieć 3 dzieci i 3 zasoby na naprawdę błędne koło.

Należy zauważyć, że trudność w opanowaniu sytuacji, w której występuje zakleszczenie. Jeśli twoje transakcje są krótkie i spójne, to impas jest bardzo trudny do osiągnięcia. Zakleszczenie wymaga (a) transakcji, która utrzymuje blokady przez dłuższy czas AND (b) transakcji, które nabywają zamki w niespójnym porządku. Zauważyłem, że najłatwiej jest zapobiec zakleszczeniom, utrzymując krótkie i spójne transakcje.

Należy również zwrócić uwagę na brak determinizmu. Nie można przewidzieć, które dziecko umrze z powodu impasu, a które będzie kontynuowane po śmierci drugiego. Tylko jeden z nich musi umrzeć, aby uwolnić potrzebne zasoby dla drugiego. Niektóre RDBMS twierdzą, że istnieje zasada oparta na liczbie zasobów przechowywanych bla bla bla, ale ogólnie, nigdy nie wiesz, w jaki sposób została wybrana ofiara.

Ponieważ dwa zapisy są w określonej kolejności, można oczekiwać, że dziecko 1 umrze jako pierwsze. Jednak nie możesz tego zagwarantować. To nie jest zakleszczenie, dopóki dziecko 2 nie spróbuje zdobyć zasobów pierwszego dziecka - sekwencja, która nabyła pierwsze, może nie określać, kto umiera.

Należy również pamiętać, że są to procesy, a nie wątki. Wątki - z powodu Pythona GIL - mogą zostać przypadkowo zsynchronizowane i wymagałyby wielu połączeń z numerem time.sleep(0.001), aby dać drugiemu wątkowi szansę na nadrobienie zaległości. Procesy - w tym przypadku - są nieco prostsze, ponieważ są w pełni niezależne.

2

Oto niektóre Pseudokod dla jak to zrobić w PHP:

Scenariusz 1:

START TRANSACTION; 
INSERT INTO table <anything you want>; 
SLEEP(5); 
UPDATE table SET field = 'foo'; 
COMMIT; 

Scenariusz 2:

START TRANSACTION; 
UPDATE table SET field = 'foo'; 
SLEEP(5); 
INSERT INTO table <anything you want>; 
COMMIT; 

Execute skrypt 1, a następnie natychmiast wykonaj skrypt 2 w innym miejscu er terminal. Dostaniesz zakleszczenie, jeśli tabela bazy danych zawiera już jakieś dane (Innymi słowy, zaczyna blokowanie po drugim użyciu).

Należy pamiętać, że jeśli mysql nie będzie honorował polecenia SLEEP(), użyj odpowiednika Pythona w samej aplikacji.