Skip to content

Same Name

REPEATABLE READ、なにそれ。という時期がわたしにもあった。

トランザクション分離レベルなんて気にしなくても、アプリケーションは動く。動いてしまう。問題が起きるのは決まって本番で、しかも再現しにくい。だから厄介なのだ。

あるとき決済まわりのコードを触ることになって、ようやく向き合った。MySQLのデフォルトはREPEATABLE READだ。同じトランザクション内なら何度読んでも同じ結果が返る。教科書にはそう書いてある。実際その通りに動く。普通に読んでいる限りは。

問題はロックだ。SELECT ... FOR UPDATEを発行した瞬間、見える世界が変わる。スナップショットの外に出て、他のトランザクションがコミットした最新のデータが見えるようになる。さっきまで存在しなかった行が現れる。Repeatableが、Repeatableではなくなる。

これだけでも混乱するのに、InnoDBにはギャップロックという仕組みがある。インデックスのレコードとレコードの間、存在しない行の「隙間」をロックする。隙間に新しい行を挿入されないようにするためだ。存在しないものをロックしている。ネクストキーロックはレコードとその直前のギャップを同時に押さえる。二つのトランザクションが異なる順序でこれを取ると、デッドロックになる。

ひと通り痛い目を見てから、プロジェクトに入ってくる若手には最初にこの話をするようにしていた。たいていはトランザクション分離レベルから説明するハメになった。SQL標準はアノマリーの定義しかしていないから、どう防ぐかはデータベースごとに違うという前提から入る必要がある。同じREPEATABLE READという名前でも、MySQLとPostgreSQLでは中身が別物だ。

名前が同じでも中身が同じとは限らない。仕様書の文言が一致していても、実装は別物だったりする。大規模な決済システムでは、この洗礼を受けないとコードが書けない。最近はAIに丸投げでコードを書いているが、彼はこの洗礼を受けているのだろうか。