マルチプロセス環境で次の Python/Django コードに問題があります。キューであるかのように InnoDB テーブルを更新しようとし、最上位の値をポップします。負荷が特定のレベルに達し、複数のプロセスがこのコードに同時に含まれるまで、正常に動作します。あるプロセスが削除のためにテーブルを保持し、別のプロセスがテーブルを更新しようとすると、デッドロックが発生します。
以前 InnoDB について知らなかったのは、インデックスがテーブルから分離されているため、あるプロセスがテーブルをロックしてインデックスのロックを待機している可能性があり、別のプロセスがその逆を行ってデッドロックを作成する可能性があるということです。これは、以下のコードの UPDATE/DELETE デッドロックで発生しているようです。
簡単に言えば、コードはテーブル内の単一の行 (LIMIT 1) を更新することから開始し、他のプロセスが同じ行を更新するのを防ぎます。次に、その行を選択して他の内容を取得し、いくつかの作業を実行してから、その行をテーブルから削除します。
@classmethod
@transaction.commit_manually
def pop(cls, unique_id):
"""Retrieves the next item and removes it from the queue."""
transaction.commit() # This has the side effect of clearing the object cache
if cls.objects.all().filter(unique_id__isnull=True).count() == 0: return None
sql = "UPDATE queue SET unique_id=%d" % unique_id
sql += " WHERE unique_id IS NULL"
sql += " ORDER BY id LIMIT 1"
cursor = connection.cursor()
try:
cursor.execute(sql)
row = cursor.fetchone()
except OperationalError, oe: # deadlock, retry later
transaction.rollback()
return None
# If an item is available, it is now marked with our process_id.
# Retrieve any items with this process id
items = cls.objects.all().filter(process_id=process_id)
if len(items) == 0: # No items available
transaction.rollback()
return None
top = items[0] # there should only be one item
# ... perform some other actions here ...
top.delete() # This item is deleted from the queue
transaction.commit()
return item
前述のように、2 つのプロセスがこの同じコードを同時に実行すると、デッドロックが発生します。top.delete()
別のプロセスがクエリを実行しているときに、あるプロセスが実行を試みていUPDATE
ます。InnoDB の行レベル ロックは、2 番目のプロセスがここでテーブルを更新しようとするのを妨げません。からの出力は次のshow engine innodb status;
とおりです。
------------------------
LATEST DETECTED DEADLOCK
------------------------
130625 13:30:22
*** (1) TRANSACTION:
TRANSACTION 0 821802296, ACTIVE 0 sec, process no 27565, OS thread id 139724816692992 starting index read
mysql tables in use 2, locked 2
LOCK WAIT 38 lock struct(s), heap size 14320, 14936 row lock(s)
MySQL thread id 2984294, query id 3142696474 example.com 10.0.1.1 mysql Sending data
UPDATE queue SET unique_id=100804 WHERE unique_id IS NULL ORDER BY id LIMIT 1
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1476 page no 2469 n bits 280 index `PRIMARY` of table `mysql`.`queue` trx id 0 821802296 lock_mode X locks rec but not gap waiting
Record lock, heap no 208 PHYSICAL RECORD: n_fields 9; compact format; info bits 32
0: len 4; hex 80290a1b; asc ) ;; 1: len 6; hex 000030fbb547; asc 0 G;; 2: len 7; hex 0000002a471282; asc *G ;; 3: len 4; hex 80000005; asc ;; 4: len 4; hex 800
00051; asc Q;; 5: len 4; hex 07a27ca6; asc | ;; 6: len 8; hex 8000124f06c3361e; asc O 6 ;; 7: SQL NULL; 8: SQL NULL;
*** (2) TRANSACTION:
TRANSACTION 0 821802311, ACTIVE 0 sec, process no 27565, OS thread id 139724817225472 updating or deleting, thread declared inside InnoDB 499
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1216, 2 row lock(s), undo log entries 1
MySQL thread id 2984268, query id 3142696582 example.com 10.0.1.1 mysql updating
DELETE FROM `queue` WHERE `id` IN (2689563)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1476 page no 2469 n bits 280 index `PRIMARY` of table `mysql`.`queue` trx id 0 821802311 lock_mode X locks rec but not gap
Record lock, heap no 208 PHYSICAL RECORD: n_fields 9; compact format; info bits 32
0: len 4; hex 80290a1b; asc ) ;; 1: len 6; hex 000030fbb547; asc 0 G;; 2: len 7; hex 0000002a471282; asc *G ;; 3: len 4; hex 80000005; asc ;; 4: len 4; hex 800
00051; asc Q;; 5: len 4; hex 07a27ca6; asc | ;; 6: len 8; hex 8000124f06c3361e; asc O 6 ;; 7: SQL NULL; 8: SQL NULL;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1476 page no 351 n bits 1200 index `queue_queue_id` of table `mysql`.`queue` trx id 0 821802311 lock_mode X locks rec but not gap waiting
Record lock, heap no 1128 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000005; asc ;; 1: len 4; hex 80290a1b; asc ) ;;
*** WE ROLL BACK TRANSACTION (2)
pop()
私の質問は、Python/Django と InnoDB でキューを実装して操作を実行する正しい方法は何ですか? 特に、このコードにどのような変更を加える必要がありますか?