やる気がストロングZERO

やる気のストロングスタイル

トランザクション内で「重複チェック」してから「DB登録実行」しても重複が発生する場合がある

例えば「ユーザー名」など重複を許さない項目はDBへ登録クエリを投げる前にバリデーションで重複チェックするが、普通にやっていると「重複チェックをパスしたのに、DB登録実行時点では重複が発生している」となるケースが存在する。

重複チェック」と「DB登録実行」が同一トランザクション内に記載されていても起こる。
※ファントムリードが起こるために発生する。

二人(A君、B君)が別の画面から同時に同じユーザー名で登録を実行した場合

A君:重複チェック => OK(重複は存在しない)
B君:重複チェック => OK(重複は存在しない)
A君:DB登録実行 => OK(重複は存在しない)
B君:DB登録実行 => NG(A君の登録したユーザー名と重複)

この状況を防ぐには、「重複チェック」の際に対象テーブルに対してテーブルロックをかける必要がある。 そうすれば、イメージ通りの挙動になる。(が、非推奨) (また、isolation levelをserializableにすることで下記イメージで実行されることを期待したが違った。 参照 => トランザクション分離レベル Serializableでも同時実行でエラーがでる可能性がある - やる気がストロングZERO

A君:重複チェック => OK(重複は存在しない)※ここでテーブルロックをかける
B君:重複チェック => テーブルロック開放待ち
A君:DB登録実行 => OK(重複は存在しない)※テーブルロックが開放される
B君:重複チェック => NG(A君の登録したユーザー名と重複)

ただしこれだと、あまり起こらないケースへの対応のために常時のパフォーマンスが犠牲になっている。

潔癖に「バリデーションでキッチリ不整合を拾いたい」といろいろ考えたが、 このケースでは単純に「DB登録を失敗させて、再度ユーザーに操作を促す」が正解だと思った。

(2020/12/14)追記:

アプリケーション側での対応を検討した。

yaruki-strong-zero.hatenablog.jp