ITの教科書検索
入門読了 約9分

トランザクション分離レベルとは ― 4段階の違い

分離レベルは、実行中のほかのトランザクションの変更を、どこまで見せるかを決める設定です。3つの読み取りの現象と4段階の違い、PostgreSQLとMySQLの既定値が分かります。

目次
  1. 分離レベルとは
  2. 3つの現象の違い
  3. 4つの分離レベルと防げる現象
  4. 上げるほど増える待ちとやり直し
  5. PostgreSQLとMySQLの違い
  6. SQLで分離レベルを指定する
  7. 練習問題

トランザクション分離レベルとは、ほかの変更がどこまで見えるかの段階

トランザクション分離レベルとは、同時に動くトランザクションどうしが、互いの途中の変更をどこまで見てよいかを決める段階です。SQL標準は、READ UNCOMMITTED・READ COMMITTED・REPEATABLE READ・SERIALIZABLEの4つを定めています。同じ表を何人かが同時に読み書きするとき、読んだ値が途中で変わってもよいかを、このレベルで選べます。

ACID特性の独立性は、ほかのトランザクションの途中の変更が見えない性質です。分離レベルは、この独立性をどこまで厳しく守るかを選ぶ段階です。

最も厳しいSERIALIZABLEでは、同時に動いても、1つずつ順に実行したのと同じ結果になります。ほかの3つのレベルは、次の節で説明する3つの現象のうち、どれを防ぐかで区別されます。

ダーティリード・ノンリピータブルリード・ファントムリードの違い

3つの現象は、どれもトランザクションAが読んでいる間に、トランザクションBが書き込むと起きます。違いは、Aの読み取りがBのどの操作の後に変わるかです。次の図は、在庫表の数量が最初10だった例を時間の順に並べたものです。

ダーティリードA:読む側B:書く側7に更新読む:7取り消し見えるノンリピータブルリードA:読む側B:書く側読む:107に更新コミット読む:7見えるファントムリードA:読む側B:書く側検索:1行行を追加コミット検索:2行見えるダーティリードA:読む側B:書く側7に更新読む:7取り消し見えるノンリピータブルリードA:読む側B:書く側読む:107に更新コミット読む:7見えるファントムリードA:読む側B:書く側検索:1行行を追加コミット検索:2行見える
在庫表の数量を、最初は10とした例です。上から下へ時間が進み、色を付けた読み取りで現象が起きます。ダーティリードだけは、コミット前の値が見えます。

ダーティリードは、コミット前の値を読む

Bが数量を7に書き換え、まだコミットしていないとします。そのときAが7を読むのが、ダーティリードです。Bがロールバックすると、Aは一度も確定しなかった値を読んだことになります。

ノンリピータブルリードは、読み直すと値が変わる

Aが数量を読んで10を得た後、Bが7に書き換えてコミットします。Aが同じ行を読み直すと、今度は7が返ります。このように、同じトランザクションの中で同じ行を読み直すと値が変わる現象を、ノンリピータブルリードと呼びます。

PostgreSQLの日本語の文書は、これを反復不能読み取りと訳しています。Bがその行を削除してコミットした場合も、読み直すと行が消えているので、同じ現象に入ります。

ファントムリードは、同じ条件で検索し直すと行が増える

Aが「数量が5未満」のような条件で検索し、1行を得たとします。その後、Bが条件に合う商品を追加してコミットすると、Aが同じ条件で検索し直したときに2行が返ります。この現象を、ファントムリードと呼びます。

ノンリピータブルリードとの違いは、変わるのが読んだ行の値か、条件に合う行の集まりかです。Aが最初に読んだ行そのものは変わっていなくても、ファントムリードは起きます。

4つの分離レベルと、防げる現象の対応表

SQL標準は、分離レベルごとに、起きうる現象と起きない現象を表にしています。下の表は、SQL-92の改訂草案の表を写したものです。PostgreSQLの文書にも、同じ内容の表が載っています。

SQL標準の分離レベルと3つの現象
分離レベルダーティリードノンリピータブルリードファントムリード
READ UNCOMMITTED起きうる起きうる起きうる
READ COMMITTED起きない起きうる起きうる
REPEATABLE READ起きない起きない起きうる
SERIALIZABLE起きない起きない起きない

下のレベルほど、防ぐ現象が1つずつ増えます。READ COMMITTEDはコミット前の値を読まず、REPEATABLE READは読んだ行の値が変わらないことまでを保証します。SERIALIZABLEでは、3つの現象はどれも起きません。

表の「起きうる」は起きてもよいという意味で、標準は防がなければならない現象だけを決めています。そのため、製品は標準より多くの現象を防いでもかまいません。次の図で分離レベルと基準を選ぶと、前の図と同じ例でAの読み取りがどうなるかが表示されます。

分離レベルを選ぶ
基準を選ぶ
  • ダーティリード防ぐAはコミット済みの10を読みます。
  • ノンリピータブルリード起きうるAが読み直すと、Bがコミットした7に変わることがあります。
  • ファントムリード起きうるAが同じ条件で検索し直すと、2行に増えることがあります。
SQL標準の定め:READ COMMITTED

コミット前の値は読みません。読み直したときの変化を防ぐことまでは求めていません。

SQL-92の改訂草案は、何も指定しないときの分離レベルをSERIALIZABLEとしています。ただし、後で説明するように、実際の製品の既定値は標準と違うことがあります。

分離レベルを上げるほど、待ちとやり直しが増える

分離レベルを上げると、防げる現象は増えます。その代わり、ほかのトランザクションを待つ時間や、エラーでやり直す回数が増えます。どちらが増えるかは、製品の作りで変わります。

MySQLのInnoDBのSERIALIZABLEでは、START TRANSACTIONの後の普通のSELECTにも、共有ロックが付きます。共有ロックと専有ロックは同時に許可されません。詳しくは排他制御の記事で説明しています。そのため、書く側が読んだトランザクションの終わりを待つ場面や、読む側が書き終わりを待つ場面が増えます。

PostgreSQLのSERIALIZABLEは、REPEATABLE READより多く待たせることはありません。代わりに、順に実行した結果と食い違うおそれのある組み合わせを見つけると、エラーにして取り消します。アプリケーションは、エラーになったトランザクションを最初からやり直すように作ります。

MySQLの文書は、READ COMMITTEDやREAD UNCOMMITTEDに緩めてよい場面も挙げています。集計の報告のように、厳密さより、ロックの負荷を減らすことが大事な場面です。どのレベルを選ぶかは、防ぎたい現象と、許せる待ちの長さで決めます。

読んだ値を書き戻す更新どうしの衝突を防ぐ2つの方式は、楽観ロックと悲観ロックの記事で比べています。

PostgreSQLとMySQLでは、既定の分離レベルと動きが違う

同じ名前の分離レベルでも、製品によって既定値と実際の動きが違います。次の表は、PostgreSQLとMySQLのInnoDBを、それぞれの公式の文書で比べたものです。

PostgreSQLとMySQL(InnoDB)の分離レベル
製品既定の分離レベル動きの特徴
PostgreSQLREAD COMMITTEDREAD UNCOMMITTEDはREAD COMMITTEDと同じ動き。REPEATABLE READでもファントムリードは起きない
MySQL(InnoDB)REPEATABLE READ普通のSELECTは最初に読んだ時点のスナップショットを読む。ロックを取る文は最新の状態を使う

PostgreSQLは、READ COMMITTEDが既定

PostgreSQLの既定の分離レベルは、READ COMMITTEDです。READ UNCOMMITTEDも指定できますが、中の動きはREAD COMMITTEDと同じなので、ダーティリードは起きません。REPEATABLE READでは、最初の問合せを始めた時点のデータを読み続けるため、ファントムリードも起きません。

MySQLのInnoDBは、REPEATABLE READが既定

MySQLのInnoDBの既定は、REPEATABLE READです。ロックを取らない普通のSELECTは、そのトランザクションで最初に読んだ時点のスナップショット(ある時点のデータの写し)を読みます。そのため、普通のSELECTを繰り返すかぎり、2回目も同じ結果が返ります。

一方、FOR UPDATEを付けた読み取りやUPDATEは、ロックを取って最新の状態を使います。範囲の条件で読むときは、行と行の隙間にもロックを掛けて、ほかのセッションの挿入を止めます。MySQLの文書は、同じトランザクションの中で、ロックを取る文と取らないSELECTを混ぜることを勧めていません。混ぜると、先の普通のSELECTでは見えなかった行が、後のロックを取る読み取りでは見えることがあるためです。

MySQLのREAD COMMITTEDでは、隙間へのロックを使わないため、ファントム行の問題が起きうると文書に書かれています。MySQLのREPEATABLE READでファントムリードが起きるかは、ロックを取らない読み取りか、取る読み取りかで変わります。どの文でどの現象を防げるかは製品と文の種類で変わるので、使う製品の文書で確かめる必要があります。

SQLで分離レベルを指定するには、SET TRANSACTIONを使う

分離レベルは、SET TRANSACTION文で指定します。PostgreSQLでは、BEGINの直後、最初の問合せより前に実行します。

BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT 数量 FROM 在庫 WHERE 商品番号 = 1;
COMMIT;

PostgreSQLで、SELECTの後にSET TRANSACTIONを実行すると、エラーになりました。現在の分離レベルは、SHOW transaction_isolationで確かめられます。何も指定しない状態では、read committedが返りました。

MySQLでは、SESSIONもGLOBALも付けないSET TRANSACTIONは、次の1つのトランザクションだけに効きます。トランザクションの途中では実行できないので、START TRANSACTIONより前に書きます。

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT 数量 FROM 在庫 WHERE 商品番号 = 1;
COMMIT;

セッション全体の分離レベルは、SELECT @@SESSION.transaction_isolationで確かめられます。SET TRANSACTIONの書き方はPostgreSQLとMySQLで共通ですが、効く範囲は製品ごとに違います。

参考にした資料

練習問題(全5問)

Q1トランザクションBが値を書き換えてまだコミットしていないうちに、Aがその値を読んだ。その後Bはロールバックした。Aに起きた現象はどれ?
Q2Aが「数量が5未満の商品」を検索すると1行だった。Bが条件に合う商品を追加してコミットした後、Aが同じ条件で検索すると2行になった。この現象はどれ?
Q3SQL標準の表で、ダーティリードとノンリピータブルリードは防ぐが、ファントムリードは起きうるとされる分離レベルはどれ?
Q4PostgreSQLの説明として正しいものはどれ?
Q5SQL標準が、同時に動いても1つずつ順に実行したのと同じ結果になることを保証する分離レベルはどれ?

選んでから「採点する」を押すと、解説つきで答え合わせができます。

関連記事

おすすめの本 PR

セキュリティとデータベースは、基本情報技術者試験の対策本でまとめて学べます。

目的別の本の選び方を見る

データベースの記事を続けて読む

データベース 全36本中29本目