如何在MySQL中导致测试目的的死锁

时间:2008-11-06 18:06:13

标签: python mysql database deadlock

我想让我的Python库使用MySQLdb能够检测死锁并再试一次。我相信我编写了一个很好的解决方案,现在我想测试它。

我可以使用MySQLdb运行以创建死锁条件的最简单查询的任何想法是?

系统信息:

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

5 个答案:

答案 0 :(得分: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;

执行脚本1,然后立即在另一个终端中执行脚本2。如果数据库表中已经包含一些数据,那么您将遇到死锁(换句话说,它会在您第二次尝试此操作后开始死锁)。

请注意,如果mysql不遵守SLEEP()命令,请在应用程序本身中使用Python等效项。

答案 1 :(得分:1)

您始终可以从另一个会话(例如mysql CLI)运行LOCK TABLE tablename。这可能会成功。

在您释放或断开会话之前,它将保持锁定状态。

答案 2 :(得分:1)

我不熟悉Python,所以请原谅我错误的语言如果我说错了......但打开两个会话(在单独的窗口中,或者从单独的Python进程 - 从单独的框中工作......)然后......

。在会议A中:

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

。然后在会议B中:

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

。然后回到会话A

  Insert TableB() Values () ...

你会陷入僵局......

答案 3 :(得分: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个资源来制造真正的恶性循环。

请注意,难以设法发生死锁的情况。如果您的交易很短且一致,那么很难实现死锁。死锁需要(a)长时间持有锁的事务和(b)以不一致的顺序获取锁的事务。我发现通过保持我的交易简短和一致来防止死锁是最容易的。

还要注意非决定论。你无法预测哪个孩子会因死锁而死亡,哪个孩子会在另一个孩子死后继续死亡。只有两个中的一个需要死才能为另一个释放所需的资源。一些RDBMS声称有一个基于资源数量的规则等等等等,但总的来说,你永远不会知道受害者的选择方式。

由于两次写入按特定顺序排列,因此您希望子级1首先死亡。但是,你无法保证。在孩子2试图获得孩子1的资源之前,这不是僵局 - 先获得者的序列可能无法确定谁死亡。

另请注意,这些是进程,而不是线程。线程 - 由于Python GIL - 可能会无意中同步,并且需要大量调用time.sleep( 0.001 )才能让其他线程有机会赶上。流程 - 为此 - 稍微简单一点,因为它们是完全独立的。

答案 4 :(得分:1)