例えばユーザー名の重複登録をNGとする場合、こんな感じでバリデーションと登録処理を書くことが多い。
※疑似コードです
begin name = "山田太郎" // dbからデータを取ってみて存在しないことを確認する user = getUserByName(name) if user != null { return false } createUser(name) commit return true
でもこれだと名前が重複したユーザーが登録されてしまう可能性がある。
理由はこちら:
yaruki-strong-zero.hatenablog.jp
防ぐには
テーブルにユニーク制約をつけて、再実行処理を組み入れる
これが一番確実。
ユニーク制約があることで重複データが登録されようとしてもエラーになり絶対に入らない。
重複エラーでアプリが落ちるのを避けたければ再実行処理を入れる。
name = "山田太郎" execCount = 0 maxTryCount = 2 while { execCount++ if execCount > maxTryCount { raise("許容再実行回数を超えました") } begin user = getUserByName(name) if user != null { return false } try { createUser(name) } catch(e) { // 重複登録エラーが出たら再実行する if execCount < maxTryCount { continue } else { raise e } } commit } return true
テーブルにユニーク制約をつけられない場合はトランザクション分離レベルをSERIALIZABLEにして再実行処理を入れる。
delete flagで運用しているなど、ユニーク制約をつけられない場合(削除ユーザーで使用されていた名前は再度使えるようになる等)はトランザクション分離レベルをSERIALIZABLEにして再実行処理を入れる。
SERIALIZABLEだと、仮にバリデーションをすり抜けて重複データのInsert処理が実行されてもcommit時にエラーが出る。
※どの程度パフォーマンスに影響が出るのかは調べてない。
※REPEATABLE READではエラーが出ず、重複登録されてしまう。
name = "山田太郎" execCount = 0 maxTryCount = 2 while { execCount++ if execCount > maxTryCount { raise("許容再実行回数を超えました") } begin SERIALIZABLE // シリアライザブル指定 user = getUserByName(name) if user != null { return false } createUser(name) try { commit } catch(e) { // エラーが出たら再実行する if execCount < maxTryCount { continue } else { raise e } } } return true
追記: このスライドの中でも触れられている。