为什么没人关心这个MySQLdb错误?这是个错误吗?
简而言之:我发现了一个错误并提供了修复方案,但到现在为止没有人给我任何反馈。我在想这个问题到底算不算错误。这不是抱怨。请看一下,如果你可能会受到影响,请检查一下这个修复。
几周前(编辑:6周前),我发现并报告了这个MySQLdb的错误,提交了修复方案,还在几个ORM的论坛上发帖,给MySQLdb的作者发了邮件,给一些讨论死锁处理的人发了邮件,给ORM的作者发了邮件,但我仍然在等待任何反馈。
这个错误让我很苦恼,我能找到的反馈只有两种解释:要么没人用Python和MySQL一起使用“SELECT ... FOR UPDATE”,要么这个根本不是个错误。
问题的核心是,当使用MySQLdb的游标执行“SELECT ... FOR UPDATE”时,死锁和“锁等待超时”的异常并没有被抛出。相反,语句静默失败,返回一个空的结果集,任何应用程序都会把它解读为没有匹配的行。
我测试了SVN版本,问题依然存在。在Ubuntu Intrepid、Jaunty和Debian Lenny的默认安装上也有这个问题。通过easy_install安装的当前版本(1.2.3c1)也受影响。
这也影响了SQLAlchemy和SQLObject,可能任何使用MySQLdb游标的ORM都会受到影响。
这个脚本可以重现一个会触发这个错误的死锁(只需在get_conn中更改用户名/密码,它会创建必要的表):
import time
import threading
import traceback
import logging
import MySQLdb
def get_conn():
return MySQLdb.connect(host='localhost', db='TESTS',
user='tito', passwd='testing123')
class DeadlockTestThread(threading.Thread):
def __init__(self, order):
super(DeadlockTestThread, self).__init__()
self.first_select_done = threading.Event()
self.do_the_second_one = threading.Event()
self.order = order
def log(self, msg):
logging.info('%s: %s' % (self.getName(), msg))
def run(self):
db = get_conn()
c = db.cursor()
c.execute('BEGIN;')
query = 'SELECT * FROM locktest%i FOR UPDATE;'
try:
try:
c.execute(query % self.order[0])
self.first_select_done.set()
self.do_the_second_one.wait()
c.execute(query % self.order[1])
self.log('2nd SELECT OK, we got %i rows' % len(c.fetchall()))
c.execute('SHOW WARNINGS;')
self.log('SHOW WARNINGS: %s' % str(c.fetchall()))
except:
self.log('Failed! Rolling back')
c.execute('ROLLBACK;')
raise
else:
c.execute('COMMIT;')
finally:
c.close()
db.close()
def init():
db = get_conn()
# Create the tables.
c = db.cursor()
c.execute('DROP TABLE IF EXISTS locktest1;')
c.execute('DROP TABLE IF EXISTS locktest2;')
c.execute('''CREATE TABLE locktest1 (
a int(11), PRIMARY KEY(a)
) ENGINE=innodb;''')
c.execute('''CREATE TABLE locktest2 (
a int(11), PRIMARY KEY(a)
) ENGINE=innodb;''')
c.close()
# Insert some data.
c = db.cursor()
c.execute('BEGIN;')
c.execute('INSERT INTO locktest1 VALUES (123456);')
c.execute('INSERT INTO locktest2 VALUES (123456);')
c.execute('COMMIT;')
c.close()
db.close()
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
init()
t1 = DeadlockTestThread(order=[1, 2])
t2 = DeadlockTestThread(order=[2, 1])
t1.start()
t2.start()
# Wait till both threads did the 1st select.
t1.first_select_done.wait()
t2.first_select_done.wait()
# Let thread 1 continue, it will get wait for the lock
# at this point.
t1.do_the_second_one.set()
# Just make sure thread 1 is waiting for the lock.
time.sleep(0.1)
# This will trigger the deadlock and thread-2 will
# fail silently, getting 0 rows.
t2.do_the_second_one.set()
t1.join()
t2.join()
在未修复的MySQLdb上运行这个脚本的输出是:
$ python bug_mysqldb_deadlock.py
INFO:root:Thread-2: 2nd SELECT OK, we got 0 rows
INFO:root:Thread-2: SHOW WARNINGS: (('Error', 1213L, 'Deadlock found when trying to get lock; try restarting transaction'),)
INFO:root:Thread-1: 2nd SELECT OK, we got 1 rows
INFO:root:Thread-1: SHOW WARNINGS: ()
你可以看到,Thread-2从一个我们知道有1行的表中得到了0行,只有执行“SHOW WARNINGS”语句才能看到发生了什么。如果你检查“SHOW ENGINE INNODB STATUS”,你会在日志中看到这一行“*** WE ROLL BACK TRANSACTION (2)”,在Thread-2上失败的选择之后发生的所有事情都是在一个半回滚的事务中。
在应用了修复后(查看下面的链接获取修复),运行这个脚本的输出是:
$ python bug_mysqldb_deadlock.py
INFO:root:Thread-2: Failed! Rolling back
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python2.4/threading.py", line 442, in __bootstrap
self.run()
File "bug_mysqldb_deadlock.py", line 33, in run
c.execute(query % self.order[1])
File "/home/koba/Desarollo/InetPub/IBSRL/VirtualEnv-1.0-p2.4/lib/python2.4/site-packages/MySQL_python-1.2.2-py2.4-linux-x86_64.egg/MySQLdb/cursors.py", line 178, in execute
self.errorhandler(self, exc, value)
File "/home/koba/Desarollo/InetPub/IBSRL/VirtualEnv-1.0-p2.4/lib/python2.4/site-packages/MySQL_python-1.2.2-py2.4-linux-x86_64.egg/MySQLdb/connections.py", line 35, in defaulterrorhandler
raise errorclass, errorvalue
OperationalError: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
INFO:root:Thread-1: 2nd SELECT OK, we got 1 rows
INFO:root:Thread-1: SHOW WARNINGS: ()
在这种情况下,Thread-2抛出了一个异常,并且正确地进行了回滚。
那么,你怎么看?这是个错误吗?没人关心,还是我只是疯了?
这是我在SF上打开的工单: http://sourceforge.net/tracker/index.php?func=detail&aid=2776267&group_id=22307&atid=374932
1 个回答
为什么没人关心这个 MySQLdb 的 bug 呢?
处理 bug 可能需要一些时间,包括确定问题的优先级、研究、验证问题、找到解决办法、测试这个解决办法,确保这个解决办法不会引起其他问题。我建议你先使用一个临时的解决方案,因为这个修复可能需要一段时间才能到你这里。