まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。
この広告は、90日以上更新していないブログに表示しています。
(注記) 去年書いて下書き状態になってた記事をそのまま公開した。MySQL 8.0 公開前に書いた内容なのでMySQL 5.7 について言及しているが、多分MySQL 8.0 でも基本的には変わらない気がする。
DB 上に指定の ID の行が存在していればUPDATE
して、存在していなければINSERT
したい、ということは web アプリケーションを書いているとよくあること。SQL:2003 標準にはMERGE
が導入されていて、Oracle Database なんかだとMERGE
を使用できるようだが、MySQL では使えない (バージョン 5.7 時点)。 (代わりにINSERT ... ON DUPLICATE KEY UPDATE
がある。)
また、複数のテーブルで 1 つの集約*1 を表現していて、集約の更新時に不整合が起きないようにロックをかけたいということも多い。
といったあたりで、MySQL (InnoDB) における参照や挿入、更新とトランザクション分離レベルやロックについて調べたのでまとめておく。 (初心者が調べてみたぐらいの内容なので、指摘やコメント等頂けると嬉しいです!)
いろいろ調べた結論としては、個人的には以下のような感じがいいのかなーと思っている。
SELECT FOR UPDATE
で行う。INSERT
する。 Duplicate error になったら (すなわち同時に実行されていた他のトランザクションによる挿入が行われた場合は)トランザクション自体を再開してやり直すか、エラーということでそのまま処理を終了する。 → こちらも挿入に成功すれば排他ロックを獲得するので、そのまま処理を進めて良い。 Duplicate error になった場合には対象行の共有ロックを獲得してしまうため、やり直す場合にはトランザクション自体をやり直さなければならない*3。INSERT ... ON DUPLICATE KEY UPDATE
で良い場面が多そう。INSERT IGNORE
は良くないという話と、INSERT or UPDATE の方法について。 ちなみに 『DELETE
してINSERT
』 は、REPEATABLE READ で対象の行が存在しない場合 (DELETE
で 1 件も削除されないとき) に、デッドロックが発生しうる*4 気がする。以下、自分が知らなくて調べた話をだらだら書いておく。
MySQL 5.7 の英語ドキュメントにまとまっている。
InnoDB では、インデックスに対するロックによって行レベルロックを実現している*5。MySQL の公式ドキュメントやかみぽさんの解説がわかりやすい。
READ COMMITTED
にすると、ギャップロックは無効化される。INSERT
操作時に、行の挿入前に獲得されるギャップロックの一種として、挿入インテンションロック (Insert Intention Locks) がある。
『MySQLでINSERTのデッドロックに嵌る人を1人でも減らすために - ichirin2501's diary』 におけるデッドロックは、別のトランザクションが行を挿入して獲得した排他レコードロックと、挿入しようとしている行のための挿入インテンションロックの衝突が 2 箇所で起こってしまう、ということなのかな? と思ったりしたけど、InnoDB monitor の出力を見た感じでは共有レコードロック待ちになってたので挿入インテンションロック待ちとは違ってそうだった。 挿入インテンションロックは純粋にギャップロックとのみ衝突するもので、レコードロックとは衝突しない模様。
ファントムリードを防ぐために
ギャップロックと挿入インテンションロックのデッドロック :mysqlのネクストキーロックと挿入インテンションギャップロックのデッドロックを確認する | ++頭道++
mysqlのネクストキーロックと挿入インテンションギャップロックのデッドロックを確認する | ++頭道++ → この人の解釈ではDELETE
により指定の ID に対するロックができる (存在しないレコードに対するロックという解釈をしている) となっているが、私の手元のMySQL 5.7.20 で試した限りでは、存在しない ID を指定してのDELETE
でギャップロックが獲得されていた。
多分他にも書きたいことがありそうだったけどもはや何も覚えてないのでこのまま公開する。
*1:DDD でいうところの集約。 Aggregates。
*2:この理由は主にギャップロック無効化のため。 ギャップロック有効だとデッドロックしうる; REPEATABLE READ でもギャップロック無効化できるしそうする方がいいのかもしれない
*3:そうしなければ、複数トランザクションで同一行に対する duplicate error が起こった場合にトランザクションをやり直さないと排他ロックの獲得ができず、デッドロックになる
*4:複数のトランザクションが DELETE するとそれぞれのトランザクションがギャップロックを獲得し、その後の INSERT がそれぞれ待ち状態になってしまう。
*5:という理解であってるよね?
*6:空のインデックスでも全体に対するギャップロックがあり得る、ってことかな?
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。