TL; DR:我已经为我发现的一个bug提供了一个补丁,我得到了0个反馈.我想知道这是不是一个错误.这不是咆哮.请阅读此内容,如果您可能受其影响,请检查修复程序.
几周前我发现并报告了这个MySQLdb错误(编辑:6周前),发送补丁,发布在几个ORM的论坛上,邮寄了MySQLdb作者,邮寄了一些人谈论处理死锁,邮寄ORM作者和我我还在等待任何反馈.
这个bug给我带来了很多的悲伤,我可以在反馈中找到的唯一解释是,无论是在python中使用mysql还是没有人使用"SELECT ... FOR UPDATE",或者这不是一个bug.
基本上问题是当使用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从我们知道的表中得到0行,并且只发出"SHOW WARNINGS"语句,您可以看到发生了什么.如果选中"SHOW ENGINE INNODB STATUS",您将在日志"***WE ROLL BACK TRANSACTION(2)"中看到此行,在Thread-2上的选择失败后发生的所有事情都是半回滚事务.
应用补丁(检查票证,下面是url)后,这是运行脚本的输出:
$ 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
为什么没有人关心这个MySQLdb错误?
错误可能需要一段时间来确定优先级,研究,验证问题,找到修复程序,测试修复程序,确保修复程序不会破坏其他任何内容.我建议你部署一个解决方案,因为这个修复程序可能需要一些时间才能到达.