我想让我的 Python 库与 MySQLdb 一起使用能够检测死锁并重试。我相信我已经编写了一个很好的解决方案,现在我想对其进行测试。
对于我可以使用 MySQLdb 运行以创建死锁条件的最简单查询的任何想法是什么?
系统信息:
- MySQL 5.0.19
- 客户端 5.1.11
- 视窗
- Python 2.4 / MySQLdb 1.2.1 p2
这是我如何在 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;
执行脚本 1,然后立即在另一个终端执行脚本 2。如果数据库表中已经有一些数据,您将遇到死锁(换句话说,它在您第二次尝试此操作后开始死锁)。
请注意,如果 mysql 不支持 SLEEP() 命令,请在应用程序本身中使用 Python 的等效命令。
我不熟悉 Python,所以请原谅我的语言不正确如果我说错了......但是打开两个会话(在单独的窗口中,或从单独的 Python 进程中 - 从单独的框可以工作......)然后.. .
. 在会话 A 中:
Begin Transaction
Insert TableA() Values()...
. 然后在会话 B 中:
Begin Transaction
Insert TableB() Values()...
Insert TableA() Values() ...
. 然后返回会话 A
Insert TableB() Values () ...
你会陷入僵局...
您始终可以从另一个会话(例如 mysql CLI)运行 LOCK TABLE 表名。这可能会奏效。
在您释放它或断开会话之前,它将保持锁定状态。
您想要以下内容。
父.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()
孩子.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 个资源,从而形成一个真正的恶性循环。
请注意,难以设计发生死锁的情况。如果您的事务很短且一致,则很难实现死锁。死锁需要(a)长时间持有锁的事务和(b)以不一致的顺序获取锁的事务。我发现通过保持交易简短和一致来防止死锁是最容易的。
还要注意非确定性。您无法预测哪个孩子会因死锁而死,哪个孩子会在另一个孩子死后继续。两者中只有一个需要死亡才能为另一个释放所需的资源。一些 RDBMS 声称有一个基于持有的资源数量的规则等等等等,但一般来说,你永远不会知道受害者是如何选择的。
由于这两个写入是按特定顺序进行的,您有点期望孩子 1 先死。但是,您不能保证。直到孩子 2 试图获得孩子 1 的资源,这并不是死锁——谁先获得的顺序可能无法决定谁死。
另请注意,这些是进程,而不是线程。由于 Python GIL 的原因,线程可能会无意中同步,并且需要大量调用才能time.sleep( 0.001 )
让其他线程有机会赶上。流程——为此——稍微简单一些,因为它们是完全独立的。