2008-11-06 2 views
8

Я хочу, чтобы моя библиотека Python, работающая с MySQLdb, могла обнаруживать тупики и повторять попытку. Я считаю, что я закодировал хорошее решение, и теперь я хочу проверить его.Как я могу вызвать тупик в MySQL для целей тестирования

Любые идеи для простейших запросов, которые я мог бы запустить с использованием MySQLdb, чтобы создать условие взаимоблокировки?

Информация о системе:

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

ответ

1

вы всегда можете запустить LOCK TABLE tablename из другого сеанса (например, CLI mysql). Это может сделать трюк.

Он останется заблокированным до тех пор, пока вы его не отпустите или не отключите сеанс.

+0

Это просто вызовет OperationalError: (1205, «Тайм-аут блокировки ожидания превышен, попробуйте перезапустить транзакцию»), нет? – Greg 2008-11-06 22:10:52

+0

@Greg: Я думаю, что они говорят о том, что один сеанс делает LOCK TABLE A, а другой сеанс делает LOCK TABLE B. В этом случае нужно как-то синхронизировать. Затем сеанс один пытается БЛОКИРОВАТЬ ТАБЛИЦУ B. Когда сеанс второй попытки LOCK TABLE A - он закроется. – 2008-11-07 10:56:33

1

Я не знаком с Python, поэтому извините мой неправильный язык. Если я говорю это неправильно ... но откройте две сессии (в отдельных окнах или из отдельных процессов Python - из отдельных ящиков будет работать ...) Затем ...

. В Сессии А:

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

. Затем В Сессия B:

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

. Затем вернитесь к сеансу

Insert TableB() Values() ... 

Вы получите затор ...

1

Вы хотите что-то по следующим направлениям.

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) 

Обратите внимание на симметрию. Каждый ребенок запускает один ресурс. Затем они пытаются получить чужой ресурс. Вы можете, для удовольствия, иметь 3 детей и 3 ресурсов для действительно порочного круга.

Обратите внимание, что затруднение в создании ситуации, при которой происходит тупик. Если ваши транзакции короткие и последовательные - тупик очень трудно достичь. Для взаимоблокировки требуется (а) транзакция, которая долго удерживает блокировки И (б) транзакции, которые приобретают блокировки в непоследовательном порядке. Я нашел, что проще всего предотвратить блокировки, сохраняя мои транзакции короткими и последовательными.

Также обратите внимание на недетерминизм. Вы не можете предсказать, какой ребенок умрет с тупиком, и который будет продолжаться после смерти другого. Только один из них должен умереть, чтобы освободить необходимые ресурсы для другого. Некоторые утверждения РСУБД о том, что существует правило, основанное на количестве ресурсов, принадлежащих бла-бла-бла, но в целом вы никогда не узнаете, как был выбран жертва.

Из-за того, что две записи находятся в определенном порядке, вы предпочитаете, чтобы ребенок 1 умирал первым. Однако вы не можете этого гарантировать. Это не тупик, пока ребенок 2 не попытается получить ресурсы ребенка 1 - последовательность того, кто приобрел первый, не может определить, кто умирает.

Также обратите внимание, что это процессы, а не потоки. Темы - из-за Python GIL - могут быть непреднамеренно синхронизированы и потребуют много вызовов до time.sleep(0.001), чтобы дать другому потоку шанс наверстать упущенное. Процессы - для этого - немного проще, потому что они полностью независимы.

2

Вот некоторые псевдокод для того, как я это сделать в PHP:

Сценарий 1:

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

Сценарий 2:

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

Execute сценарий 1, а затем сразу же запустить скрипт 2 в anoth er терминал. Вы получите тупик, если в таблице базы данных уже есть некоторые данные (другими словами, он начинает блокировку после второго раза, когда вы это пробуйте).

Обратите внимание, что если mysql не выполнит команду SLEEP(), используйте эквивалент Python в самом приложении.