トランザクション分離レベルとは ― 4段階の違い
分離レベルは、
トランザクション分離レベルとは、ほかの変更がどこまで見えるかの段階
トランザクション分離レベルとは、同時に動くトランザクションどうしが、互いの途中の変更をどこまで見てよいかを決める段階です。SQL標準は、READ UNCOMMITTED・READ COMMITTED・REPEATABLE READ・SERIALIZABLEの4つを定めています。同じ表を何人かが同時に読み書きするとき、読んだ値が途中で変わってもよいかを、このレベルで選べます。
ACID特性の独立性は、ほかのトランザクションの途中の変更が見えない性質です。分離レベルは、この独立性をどこまで厳しく守るかを選ぶ段階です。
最も厳しいSERIALIZABLEでは、同時に動いても、1つずつ順に実行したのと同じ結果になります。ほかの3つのレベルは、次の節で説明する3つの現象のうち、どれを防ぐかで区別されます。
ダーティリード・ノンリピータブルリード・ファントムリードの違い
3つの現象は、どれもトランザクションAが読んでいる間に、トランザクションBが書き込むと起きます。違いは、Aの読み取りがBのどの操作の後に変わるかです。次の図は、在庫表の数量が最初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の文書にも、同じ内容の表が載っています。
| 分離レベル | ダーティリード | ノンリピータブルリード | ファントムリード |
|---|---|---|---|
| READ UNCOMMITTED | 起きうる | 起きうる | 起きうる |
| READ COMMITTED | 起きない | 起きうる | 起きうる |
| REPEATABLE READ | 起きない | 起きない | 起きうる |
| SERIALIZABLE | 起きない | 起きない | 起きない |
下のレベルほど、防ぐ現象が1つずつ増えます。READ COMMITTEDはコミット前の値を読まず、REPEATABLE READは読んだ行の値が変わらないことまでを保証します。SERIALIZABLEでは、3つの現象はどれも起きません。
表の「起きうる」は起きてもよいという意味で、標準は防がなければならない現象だけを決めています。そのため、製品は標準より多くの現象を防いでもかまいません。次の図で分離レベルと基準を選ぶと、前の図と同じ例でAの読み取りがどうなるかが表示されます。
- ダーティリード起きうるBが
確定前に 書いた 7を、 Aが 読むことがあります。 - ノンリピータブルリード起きうるAが
読み直すと、 Bが コミットした 7に 変わる ことがあります。 - ファントムリード起きうるAが
同じ 条件で 検索し直すと、 2行に 増える ことがあります。
最も
- ダーティリード防ぐAは
コミット済みの 10を 読みます。 - ノンリピータブルリード起きうるAが
読み直すと、 Bが コミットした 7に 変わる ことがあります。 - ファントムリード起きうるAが
同じ 条件で 検索し直すと、 2行に 増える ことがあります。
コミット前の
- ダーティリード防ぐAは
コミット済みの 10を 読みます。 - ノンリピータブルリード防ぐAが
読み直しても 10の ままです。 - ファントムリード起きうるAが
同じ 条件で 検索し直すと、 2行に 増える ことがあります。
一度
- ダーティリード防ぐAは
コミット済みの 10を 読みます。 - ノンリピータブルリード防ぐAが
読み直しても 10の ままです。 - ファントムリード防ぐAが
検索し直しても 1行の ままです。
1つずつ順に
- ダーティリード防ぐAは
コミット済みの 10を 読みます。 - ノンリピータブルリード起きうるAが
読み直すと、 Bが コミットした 7に 変わる ことがあります。 - ファントムリード起きうるAが
同じ 条件で 検索し直すと、 2行に 増える ことがあります。
PostgreSQLの
- ダーティリード防ぐAは
コミット済みの 10を 読みます。 - ノンリピータブルリード起きうるAが
読み直すと、 Bが コミットした 7に 変わる ことがあります。 - ファントムリード起きうるAが
同じ 条件で 検索し直すと、 2行に 増える ことがあります。
PostgreSQLの
- ダーティリード防ぐAは
コミット済みの 10を 読みます。 - ノンリピータブルリード防ぐAが
読み直しても 10の ままです。 - ファントムリード防ぐAが
検索し直しても 1行の ままです。
ファントムリードも
- ダーティリード防ぐAは
コミット済みの 10を 読みます。 - ノンリピータブルリード防ぐAが
読み直しても 10の ままです。 - ファントムリード防ぐAが
検索し直しても 1行の ままです。
順に
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 | READ COMMITTED | READ UNCOMMITTEDは |
| MySQL | REPEATABLE READ | 普通の |
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で共通ですが、効く範囲は製品ごとに違います。
参考にした資料
- Database Language SQL(Proposed revised text of DIS 9075, July 1992)(4.28 SQL-transactions、Table 9)
- PostgreSQL 16 日本語ドキュメント「13.2. トランザクションの分離」(表13.1、各分離レベルの動き)
- MySQL 8.4 Reference Manual「Transaction Isolation Levels」(InnoDBの既定値と各分離レベルの動き)
- MySQL 8.4 Reference Manual「SET TRANSACTION Statement」(指定が効く範囲)
練習問題(全5問)
正解:ダーティリード
コミットされる
正解:ファントムリード
同じ
正解:REPEATABLE READ
REPEATABLE READは、
正解:REPEATABLE READでも、
PostgreSQLの
正解:SERIALIZABLE
SERIALIZABLEは、
選んでから「採点する」を押すと、解説つきで答え合わせができます。
関連記事
おすすめの本 PR
セキュリティとデータベースは、基本情報技術者試験の対策本でまとめて学べます。

