MySQL/InnoDB でデッドロック状況が発生すると、次のようなよくあるエラーが返されます。
「ロックを取得しようとしたときにデッドロックが見つかりました。トランザクションを再開してください」
そこで私がやったことは、トランザクションに入るすべてのクエリを記録して、トランザクション内のステートメントが失敗した場合に簡単に再発行できるようにすることでした。簡単です。
問題: 以前のクエリの結果に依存するクエリがある場合、これはあまりうまく機能しません。
例えば:
START TRANSACTION;
INSERT INTO some_table ...;
-- Application here gets ID of thing inserted: $id = $database->LastInsertedID()
INSERT INTO some_other_table (id,data) VALUES ($id,'foo');
COMMIT;
このような状況では、トランザクションを元の状態のまま単純に再発行することはできません。最初の SQL ステートメントで取得された ID は、トランザクションが失敗すると無効になりますが、2 番目のステートメントで使用されます。一方、多くのオブジェクトにはトランザクションからのデータが入力されており、トランザクションがロールバックされるとそれらのデータは無効になります。もちろん、アプリケーション コード自体はデータベースとともに「ロールバック」されません。
質問は、アプリケーション コードでこれらの状況をどのように処理できるかということです。(PHP)
私は 2 つのことを想定しています。私の考えが正しいかどうか教えてください。
1) データベースはあらゆる状況でトランザクションをそのまま再発行することはできないため、私のオリジナルの解決策は機能せず、使用すべきではありません。
2) これを行う唯一の良い方法は、すべてのトランザクション発行コードを独自の try/catch ブロックにラップし、SQL だけでなくコード自体の再発行を試行することです。
ご意見ありがとうございます。あなたは素晴らしいです。
ベストアンサー1
トランザクションは失敗する可能性があります。デッドロックは失敗のケースであり、シリアル化可能なレベルでも失敗する可能性があります。トランザクション分離の問題は悪夢です。失敗を回避しようとするのは悪い方法だと思います。
適切に記述されたトランザクション コードであれば、トランザクションの失敗に対して効果的に準備されているはずです。
ご覧のとおり、クエリを記録して再生することは解決策ではありません。トランザクションを再開するとデータベースが移動してしまうからです。これが有効な解決策であれば、SQL エンジンが確実にそれを実行します。私にとってのルールは次のとおりです。
- トランザクション内のすべての読み取りをやり直します(外部で読み取ったデータは変更されている可能性があります)
- 前回の試行からすべてをスローします。トランザクションの外部に書き込んだもの(ログ、LDAP、SGBD の外部のもの)がある場合は、ロールバックによりキャンセルされるはずです。
- 実際、すべてをやり直します:-)
これは、再試行ループ。
トランザクションを内部に含む try/catch ブロックがあります。3while
回程度の試行を含むループを追加する必要があります。コードのコミット部分が成功した場合は while ループを終了します。3 回の再試行後もトランザクションが失敗する場合は、ユーザーに例外を発行します。無限の再試行ループを試行しないようにするためです。実際には非常に大きな問題が発生する可能性があります。SQL エラーとロックまたはシリアル化可能な例外は、異なる方法で処理する必要があることに注意してください。3 は任意の数です。より多くの試行回数を試行できます。
次のような結果になるかもしれません:
$retry=0;
$notdone=TRUE;
while( $notdone && $retry<3 ) {
try {
$transaction->begin();
do_all_the_transaction_stuff();
$transaction->commit();
$notdone=FALSE;
} catch( Exception $e ) {
// here we could differentiate basic SQL errors and deadlock/serializable errors
$transaction->rollback();
undo_all_non_datatbase_stuff();
$retry++;
}
}
if( 3 == $retry ) {
throw new Exception("Try later, sorry, too much guys other there, or it's not your day.");
}
そして、それはすべてのもの(読み取り、書き込み、機能的なもの)が で囲まれている必要があることを意味します$do_all_the_transaction_stuff();
。トランザクション管理コードはコントローラー内にあるため、高レベルアプリケーション機能メインコード複数に分割されていない低レベルデータベースアクセスモデルオブジェクト。