TL;DR: 見つかったバグのパッチを提供しましたが、フィードバックはありません。それがバグなのかどうか疑問に思っています。これは暴言ではありません。これを読んで、影響を受ける可能性がある場合は、修正を確認してください。
私は数週間前にこの MySQLdb バグを発見して報告し (編集: 6 週間前)、パッチを送信し、ORM のいくつかのフォーラムに投稿し、MySQLdb の作成者にメールを送り、デッドロックの処理について話している人にメールを送り、ORM の作成者にメールを送りました。どんな種類のフィードバックも待っています。
このバグは私に多くの悲しみをもたらしました.フィードバックで見つけることができる唯一の説明は、mysqlでPythonで「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 がテーブルから 0 行を取得したことがわかります。1 行あることがわかっており、"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