🍽️

5分で覚えるトランザクション分離レベル

2024/11/06に公開
5

これはなに

ども、レバテック開発部のもりたです。

今回はトランザクション分離レベルについてまとめました。トランザクション分離レベルって基本情報技術者試験とかで学ぶものの、座学だけだとあんまりピンとこずに忘れちゃいますよね。もりたも長らく曖昧な状態で生きていたのですが、よい理解の仕方があったので今回はその解説をします。

トランザクション分離レベルを構成するふたつの変数

トランザクション分離レベルとは

まず初めに、概要を掴むところからいきましょう。

トランザクション分離レベルとは、あるトランザクションのデータベースに加えた変更が、他のトランザクションにどの程度影響を与えるか? というもの(分離性、独立性)を一定基準でレベルに分けてまとめたものです。

どの程度影響を受けるか? については三つの影響が定義され、その影響度合いに応じて分離レベルが4つ存在します。これは大体こんな図で解説されます。


よくある画像

コミット前後、処理内容の四象限

そして、この発生する影響は以下ふたつの要素の組み合わせです。

  • コミットの前/後
    • コミットした前が見えるか、コミットした後が見えるか
  • 更新処理/挿入削除処理
    • UPDATEの処理が見えるか、INSERT, DELETEの処理が見えるか

図にすると、こんな感じです。

今回はこの図を使って、ひとつひとつのトランザクション分離レベルの説明をします。よくこの図を覚えといてください。

四象限とトランザクション分離レベル

説明

さて、ここから個々のトランザクション分離レベルについて、具体的に説明します。先ほどの図に分離レベルを書き込んだものを先に示します。

これが全てです。コミット前の処理が見える(ダーティリード)のがRead Uncommitedで、コミット後のUPDATEが見える(ファジーリード)のがRead Commited、コミット後のINSERT/DELETEが見える(ファントムリード)のがRepeatable Readです。また冒頭の図の通り、それぞれの分離レベルはより厳しいレベルで発生する影響を含みます。[1]
なおSerializableが載っていませんが、どれも見えません。

Read Uncommited

コミット前のUPDATE/INSERT/DELETEが見えます。この不都合をダーティリードと呼びます。フォームから入力されて更新が確定していないデータのことをダーティと呼んだりするので馴染みはあるかなと思います。

他の解説記事だと更新が対象と書いてあることが多いのですが、トランザクション分離レベルが策定されたSQL92での定義だと特に処理内容の言及はなく、INSERT/DELETEを含むようです。MySQLで実際に試しても、Read UncommitedでINSERTが確認できました。

Read Commited


コミット後のUPDATEが見えます。これをファジーリードだとか、ノンリピータブルリードと呼びます。

Repeatable Read


コミット後のINSERT/DELETEが見えます。これをファントムリードと呼びます。

このトランザクション分離レベルがRepeatable Readなのは、一度読み込んだ対象行がもう一度読み込んでも同じ結果を返すため、リピート可能と言っています。

なお、MySQLのInnoDBではこの分離レベルがデフォルトです。かつ、ネクストキーロックという仕組みでファントムリードを防いでいます。

Serializable

トランザクションが直列し、並行しない分離レベルです。並行しないので、同時実行した他のトランザクションの処理は見えません(というか存在しない)。この場合はファントムリードも発生しません。

最後に

以上で簡単な説明は終わりです。
簡単な説明でしたがトランザクション分離レベルと不都合を対応させやすくなっていたら幸いです。

参考文献

SQL92

脚注
  1. この箇所、こばさんからのご指摘をいただきました。ありがとうございます!! ↩︎

レバテック開発部

Discussion

ピン留めされたアイテム
choplinchoplin

この記事では各トランザクション分離レベルで起きうる現象が整理されており、初学者向けに配慮されていると感じました。一方で、いくつかの重要なポイントが誤解を招きかねない表現になっているかもしれないため、参考までにコメントさせていただきます。あくまで建設的な意図での指摘なのですが、もしご不快に思われた場合は申し訳ないです。

まず全体のところなのですが、各トランザクションで起きうる現象を、更新あるいは挿入・削除の処理に対応付けて分類することは、ANSIの定義とも、実際の各DBの実装とも整合しません。

より正確には、ANSIの定義では特定の処理と紐づけたような書き方になってはいます。例えば、Read Uncommittedの節で引用しているリンク先では、Dirty Readを あるトランザクション(T1)がcommit or rollbackする前のデータを別のトランザクション(T2)が読んでしまう問題 としていますが、そのページからもリンクされているA Critique of ANSI SQL Isolation Levels
の論文内にANSIの定義があり、

Transaction T1 modifies a data item. Another transaction T2 then reads that data item before T1 performs a COMMIT or ROLLBACK. If T1 then performs a ROLLBACK, T2 has read a data item that was never committed and so never really existed.

となっています。この文にあるように、ANSIではDirty Readの対象として更新のみを定義しています。しかし、この論文はこのようなANSIの自然言語での定義が曖昧であると批判する立場であり、この論文に限らず各DB実装においてもDirty Readは更新のみではなく挿入・削除も対象とすることが一般的であるかと思います。

Dirty Readについては本記事との齟齬はないのですが、一方でFuzzy ReadやPhantom Readについても更新・挿入・削除のすべての処理を対象とすることが一般的であり、本記事の内容と齟齬がある点かと思います。Fuzzy ReadはANSIの定義においても削除は対象とするされていますし、クエリのWHERE句の条件に合致するように(あるいは合致しないよう)更新処理を行うことによってPhantom Readは起き得ます。

ではどのように整理するのがよいのかという点については、自然言語による曖昧さを許容するとして、Dirty ReadとFuzzy Readは1レコード内で異なる結果になることがある、Phantom Readは1レコード内では常に同じ結果になるがレコードの集合としては異なる集合になることがある、という理解がよいのではないかと思います。(あくまで私の意見です)

以上で大きな部分の指摘は終わりなのですが、一点だけ細かい指摘をすると、

Serializable
トランザクションが直列し、並行しない分離レベルです。

とありますが、これは誤りです。正しくは、各トランザクションを直列に実行した場合と同じ結果になるように実行を行う(制約を満たす範囲では並行実行してもよい)、です。細かくいうと、ANSIでは3つの現象を許容しないという形でSerializableについて述べていますが、明確な定義はないので、直列に実行した場合と同じ結果という定義を用いることが一般的です。直列に実行した場合と同じ結果になるように実行するため、3つの現象はどれも起き得ないということになります。

もりたもりた

あ〜ありがとうございます!! めちゃくちゃ勉強になりました!! 試験勉強のレベルだと触れられない知識をとても丁寧に記載していただいており、とても助かりました。
追って本文修正させていただくのと、記事冒頭でもこのコメントを紹介させていただきます🙇

こばこば

こちらの図ですが、下半分を起きる事象(濃い色)とするのが正しいのではないでしょうか。
Read Committedは、冒頭の「よくある画像」にあるようにファジーリードもファントムリードも発生します。

もりたもりた

ご指摘ありがとうございます!!!! Xの方でも返してしまったのですが、ご認識の通りです。
今回の記事はある程度知識のある人がサッと覚えられるよう、各トランザクション分離レベルとその分離レベルでギリ残っている影響との対応関係に焦点を当てています。
ただこばさんのご指摘の通り、この記事でトランザクション分離レベルに触れた人には誤解させる良くない記述なので、文章で補足させていただきます。ご指摘ありがとうございました!!
(関係ないですがこばさんが共著で参加された『マルチクラウドデータベースの教科書』も買わせていただきました!!!!!)

もりたもりた

多分これ構成を変えたら色を塗る範囲を正しくしつつ伝わりやすい感じにできると思うので、今は一旦文章で補足しつつ後ほど修正させていただきます!