おそらくはそれさえも平凡な日々

レコードがなかったらINSERTして返すみたいなのを確実にやる

find_or_create的なやつは大体どんなORMでも

  1. レコードを探す
  2. 無かったらINSERT

みたいに実装することになると思う。ただこれだと、1と2の間でレースコンディションでエラー起きることがある。他のプロセスがINSERTしてしまうとかそういうやつ。

それを防ぎたい場合に、1の時点でFOR UPDATEするのはすごくダメで、空行にFOR UPDATEしたりするとMySQLだと盛大に乙るのは有名な話。

エラーを起こさないで、確実にレコードを取りたい場合にはどうすればよいかというと、以下のようにするのが良いと思っている。UNIQUEキー制約なりがちゃんと付いている前提。サンプルコードはTengの場合。

sub find_or_create_surely {
    my ($self, $table, $where, $opt) = @_;

    my $row;
    my $txn = $self->txn_scope; {
        # データを取得
        $row = $self->single($table, $where);
        unless ($row) {
            # 無かったらINSERTを試みる
            eval {
                $row = $self->insert($table, $where);
            };
            if (my $err = $@) {
                if ($err =~ /DBD::mysql::st execute failed: Duplicate entry/) {
                    # エラーが発生していて重複エラーだったら取り直す
                    $row = $self->single($table, $where);
                }
                else {
                    # 不明なエラーなので、例外を投げる
                    $txn->rollback;
                    die $@;
                }
            }
        }
        # ロックかけたいときは取り直す(private interface使ってるのあんま良くない…)
        $row = $self->single($table, $row->_where_cond, {for_update => 1}) if $opt->{for_update};
    }
    $txn->commit;

    $row;
}

Duplicate entryの部分はここだと正規表現だけど、最近はMyApp::DB::DuplicateExceptionとかをプロジェクトで用意しているってのは前に少し書いた。

常にこうしたほうが良いという話ではなくて、これはエラーを握りつぶしているので、そういうことが起こり得て許容するのであれば上記の実装でいいけど、他のプロセスが割り込むこと自体がまずい(連打アクセスなどで同じ処理が並列に続行されてしまうとか)のであればちゃんとエラーにしたほうが良い。

なので、最初に書いた「レコードを探して」「無かったらINSERT」という処理がデフォルトの挙動になっているのは正しい。ActiveRecordもそうで、レースコンディションをケアしたいのであればActiveRecord::RecordNotUniqueをトラップするとかそういう感じになるみたい。

常にINSERT ON DUPLICATE KEY UPDATEではダメか?

状況によっては構わない。ただ、INSERT ON DUPLICATE KEY UPDATEだと以下の様な問題がある。

  • テーブルのAUTO_INCREMENTの値が常に(UPDATEの場合でも)インクリメントされてしまうので場合によってはすごい勢いで増える
  • 単純な処理だったら良いが、レコードの事前の値をとってそれを元に更新するとかができない

INSERTする所で例外をトラップするかわりにINSERT IGNOREではダメか?

INSERT IGNOREは重複エラー以外もIGNOREしてしまうのが良くない。

追記

確かに他のトランザクション内でINSERTが成功してるけどcommitはされていないみたいな状態だとレコードが取れない可能性がありますね!めんどくさい…

created at
last modified at

2014-03-02T23:50:20+0900

comments powered by Disqus