技術

MySQLでなぜかlock wait timeoutが発生するようになった話

まず、問題が起きるようになったプログラムの概要。

MySQLのデータベースを操作するとあるバッチ処理がありまして、その中でテーブルA(InnoDB)とテーブルB(MyISAM)を更新します。(実際はテーブルはもっと多いです)

テーブルAの更新→テーブルBの更新は1セットであり、それぞれ行レベルの更新であり、それが1回のバッチ処理の実行で何回も実行されます。
この1セットが1トランザクションに相当します。(オートコミットはオフ)

そしてバッチ処理の最後にテーブルAの全行を対象とした更新処理が行われます。

処理の流れは以下のとおり。

トランザクション開始

テーブルAの更新(行レベル)

テーブルBの更新(行レベル)

コミット

(繰り返し)

トランザクション開始

テーブルAの更新(行レベル)

テーブルBの更新(行レベル)

コミット

最後のトランザクション開始

テーブルAの更新(全行対象)

最後のコミット

で、このバッチ処理の最後のテーブルAの更新(全行対象)でlock wait timeoutが発生するようになりました。
今まで一瞬で終わってたのに。
タイムアウト時間を延ばしてみてもダメ。

ログを見てると途中1箇所だけテーブルBの更新でData too long for columnが発生していました。(TEXTカラム)

プログラム的には例外処理をやってるので1セット分コミットされないだけで処理は続行します。
また、アプリとしてもデータ上1セット分抜けたとしても問題ないつくりになってます。

なのであまり重要視してなかったのですが、Data too long for columnが気になるので発生しないように修正してみました。
根本的な解決ではないですがTEXTカラムをMEDIUMTEXTカラムにとりあえず変更してみました。
それによってData too long for columnが発生しなくなりましたが、なんと無関係と思ってたlock wait timeoutも発生しなくなりました。

不思議な感じがしたんですが、考えてみたら初歩的なことです。

実はバッチ処理のプログラムが1セット内で例外が発生したときにロールバックするような作りになってませんでした。

今回の場合、例外が起きたセットに着目すると、テーブルAの更新(行レベル)によって行ロックが獲得され、その次のテーブルBの更新で例外(Data too long for column)が発生したことになります。

ここで、本来ならロールバックすべきですがそうなってなかった為にテーブルAの更新(行レベル)がコミットもロールバックもされない宙ぶらりんの状態になり、行ロックがずっと握られたままになっていました。

それでも以降のテーブルAの更新(行レベル)はロックされたままの行とは別の行を対象としているので問題なく処理されます。

しかし最後のテーブルAの更新(全行対象)でロックが衝突することになり、ここで永遠に待たされることになります。(実際はタイムアウト時間が過ぎるまで待たされた後失敗します。)

MySQLに限った話ではないですねコレ。
ミスが初歩的過ぎて恥ずかしい(ビクンビクン
ロールバックだけじゃなくData too long for column対策もちゃんとやっとこう。

コメントを残す

メールアドレスが公開されることはありません。



※画像をクリックして別の画像を表示

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください