WEBサービス創造記

WEBサービスを作ったり保守したりしてる人のメモブログです。

MySQLのInnoDBストレージエンジンを使用したテーブルで意図的にデッドロックを発生させる

   

MySQLでデッドロックを発生させる

MySQLで意図的にデッドロックを発生させるテストを行いましたのでその覚書です。
MySQLのバージョンは”5.1.63-0ubuntu0.11.04.1-log”で確認しました。

まず、適当なテスト用データベースとテーブルを作成しておきます。
ストレージエンジンはトランザクションが利用可能なInnoDBを使用します。

mysql> CREATE DATABASE wide_use;
mysql> USE wide_use
mysql> CREATE TABLE deadlock_test(col int) ENGINE=InnoDB;
mysql> INSERT INTO deadlock_test VALUES(1);
mysql> INSERT INTO deadlock_test VALUES(2);

なお、このテストは行単位でロックがかかることが前提条件です。

InnoDBではロックが行単位でかかることはよく知られていると思いますが、無条件で行単位のロックが働くわけではなく、条件次第ではテーブルロックネクストキーロックというロックとなることがあるようです。
このInnoDBでのロックの仕様に関しては以下の記事がわかりやすく参考になります。
InnoDBで行ロック/テーブルロックになる条件 – (゚∀゚)o彡 sasata299’s blog

ここではInnoDBの行単位ロックを有効にするため、以下のようにテスト用カラムにユニーク制約をかけました。

mysql> ALTER TABLE `deadlock_test` ADD UNIQUE (`col`);

ここからは2つのトランザクションを並行して実行させます。
まずbeginでトランザクションを開始(以下、Tx1と記載)し、値が1のレコードを排他ロックします。

mysql_Tx1> begin;
mysql_Tx1> select * from deadlock_test where col = 1 for update;

続いて別のコンソールを立ち上げ、そこでTx1と並行して新しいトランザクションを開始(以下、Tx2と記載)します。
Tx2ではTx1とは別のレコード(値が2のレコード)を排他ロックします。

mysql_Tx2> begin;
mysql_Tx2> select * from deadlock_test where col = 2 for update;

続いて、今度はTx1で今ほどTx2で排他ロックをかけたレコードに対して排他ロックをかけます。
※あまり時間がかかりすぎると”Lock wait timeout exceeded;”エラーが発生し、タイムアウトでロックが解除されてしまうので注意しましょう。

mysql_Tx1> select * from deadlock_test where col = 2 for update;

最後にTx2でTx1で一番最初に排他ロックを行ったレコード(値が1のレコード)に対して排他ロックをかけます。

mysql_Tx2> select * from deadlock_test where col = 1 for update;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

タイムアウトする前ならMySQLがデッドロックを検出し、上記のようなエラーメッセージが出力されます。

 - MySQL , , , ,