ぱいぱいにっき

Pythonが好きすぎるけれど、今からPerlを好きになりますにっき

レコードなかったらINSERTして返すみたいなのする時に先にSELECTするとよく分かんない感じになる件について

相変わらずよくわかんないタイトルです。どうもマコピーです。

レコードがなかったらINSERTして返すみたいなのを確実にやる | おそらくはそれさえも平凡な日々
という記事があるのですが、INSERTする前にトランザクション内で先に読むとよく分かんない感じになって困るみたいな話をさせていただきます。

トランザクション分離レベルはREPEATABLE-READです。
トランザクション内で以下の手順でなかったらINSERTするみたいな処理があったとして

  1. SELECTする。あったらそのまま返す。
  2. 空だったらINSERTする。
  3. INSERTしたやつをSELECTする。

これが並列で走った時にどうなるか。それが以下の感じです。(最後にexit連発するのがダサい……)

上段が一番初めになかったらINSERTする的なことをやっていて、中段も同様の処理。下段がINSERTする前にSELECTするのをやめたバージョンです。

  1. BEGINは3つ同時に開始。
  2. 上段がINSERTする前に中段がSELECTして当然ですけれど無い。
  3. 上段がINSERTしてCOMMIT。
  4. 中段がもう一回SELECTしてみる。無い
  5. 無いのでINSERTしてみる。Duplicate。裏でコミットされているので
  6. 例えば上の記事のようにDuplicateをキャプチャしてSELECTする。でも無い。困る

一方一番初めのSELECTを抜かした下段はどうかというとSELECTでちゃんと引っ張ってこれます。
結局のところトランザクション始めたあとにSELECTしちゃダメで、なんか結局初回以外は常にDuplicateする前提で先にINSERTすればいいのでは?ということになったのですが、気持ち悪い感じです。

どうすればいいのか、イケている方法ないでしょうか。