mysqlのロック方法について一つ賢くなった

@nqounetです。

mysqlで運用しているサービスで、1日に2,3回ほどDeadlockによってエラーが発生するという問題がありました。

その問題が解決したのでメモを残しておきます。

最初にまとめ

mysqlのInnoDBでdeleteするのは、削除対象のレコードがある場合のみにする必要がある。

delete時に対象レコードがない場合、ギャップロックがかかりinsertが通らなくなる。

実装のちょっとした手抜きが頭を悩ます、という良い(悪い?)例になった。

参考

まずかった実装

モデルケースとしてはupdateをdelete->insertにしていた感じの部分。

イメージはこんな感じ。

1
2
3
sub update {
    shift->delete(条件)->insert(新しい行);
}

書き込む行数が変わる場合があるので毎回deleteしてからinsertしていた。

最初はinsertとupdate(delete->insert)を別々で使っていたんだけど、前処理を共通化して、insertのみの場合でもupdateするようにした。

何が問題だったか

ここで結果的に問題になったのが、最初に作成する時。

最初に作成する場合はdelete対象がない。

そのためinsertを通さないロックがかかってしまい、たまたまinsertのプロセスとかぶった場合にDeadlockが発生していた。

とりあえずの解決策

最初に作成する場合はdeleteしない、という方法もあったのですが、より汎用化するため、deleteの処理を変えることにしました。

1
2
3
4
5
6
sub delete {
    my @rows = shift->select(条件);
    for my $row (@rows) {
        $row->delete;
    }
}

一見、冗長な気がするのですが、一旦条件に合う行を抽出してから削除することで適切にロックがかかるようになり、Deadlockによるエラーが再現しないようになりました。

あとからこの部分を見た時、冗長だなーと思って単純なdeleteに戻しそうなので注意する必要がありますね。

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy