トランザクション分離レベルについて


 

トランザクションとは

作業単位として扱う一連の操作の集まりをトランザクションという。

トランザクションによって
・一連の全て操作が実行される
・一連の全て操作が全てキャンセルされる
(何も行っていない元の状態を復元)
を保証されることになる。

中途半端な状態は有り得なくなる。

トランザクションはそれ以上細かい単位に分割することができない作業単位である。
 
 

分離性に関する概念

2つのトランザクション(仮にAとB)が同時に一つのテーブルを操作する
ことにより起こりうる影響に下記のものがある。

ダーティリード (Dirty Read)

一方のトランザクションBでコミットされていないデータを他方のトランザクションAで読み取ってしまう問題が起きる。
 
 

ファジーリード/ノンリピータブルリード (Fuzzy Read / Non-Repeatable Read)

トランザクションAでデータを複数回読み取っている途中で、トランザクションBがデータを更新してコミットした場合、トランザクションAで違う結果のデータを読み取ってしまう問題が起きる。(非再現リードとも呼ぶ)

ファントムリード (Phantom Read)

トランザクションAで一定の範囲のレコードに対して処理を行っている途中で、トランザクションBでデータを追加・削除してコミットした場合、トランザクションAで幻影のようにデータが反映される。

そのため、処理の結果が変わってしまう問題が起きる。

これらの問題をどの程度許容するかがトランザクション分離レベル。

トランザクション分離レベルの種類

分離レベルは4種類あり、それぞれの分離レベルで発生する問題は以下の通り。

○:発生する ×:発生しない

 
 

MariaDBで実際に試す

MySQL(InnoDB)でトランザクション分離レベルを実際に試してみます。

準備

プレイヤーのコイン数を表す簡単なテーブルを作る。

テーブル作成スクリプト

drop table coin;

create table coin
(id int primary key auto_increment,
player_id varchar(50) not null,
quantity int not null default 0);

insert into coin values(1,'player1',1000);
insert into coin values(2,'player2',1000);
desc coin;
select * from coin;

テーブル構造は次の通り。

 
 
2つのターミナルからMariaDBLに接続し、それぞれプレイヤーAとプライヤーBとする(以下はAとBと呼ぶ)。
これで準備は完了!


 
 

READ UNCOMMITTED

コミットされていない変更を他のトランザクションから参照できる。

① 【A】でトランザクション分離レベルをREAD UNCOMMITTEDに設定し、テーブルを検索する。

 
 

② 【B】でもREAD UNCOMMITTEDに設定し、player1のデータを更新しする(未コミット)。

 
 

③ 【A】再度テーブルを検索する。

この時点で【B】で更新したデータがコミットされていないにもかかわらず、【A】では更新データを読み取とった。ダーティリードの発生。

 
 

READ COMMITTED

コミットされた変更を他のトランザクションから参照できる設定。
READ COMMITTEDが多くのデータベース(Oracle、PostgreSQL、SQL Server)でデフォルトのトランザクション分離レベルになっている。

① 【A】で分離レベルをREAD COMMITTEDに設定し、テーブルを検索する。

 
 

② 【B】でもREAD COMMITTEDに設定し、player1のデータを更新する(未コミット)。

 
 

③ 【A】で再度テーブルを検索する。

ダーティリードは発生しなくなった。

 
 

④ 【B】でコミットする。


 
 

⑤ 【A】で再度テーブルを検索する。

変更後の参照をしたことによって前回と違う結果になった。
つまり、ファジーリードが発生した。

 
 

⑥ 【A】でコイン数の合計値を検索する。

 
 

⑦ 【B】でテーブルにplayer3のデータを追加してコミットする。

 
 

⑧ 【A】でコイン数の合計値を再度検索する。

player3のデータが参照できたことによって前回と違う結果になった。
つまり、ファントムリードが発生した。

このようにファジーリードやファントムリードは相手のタイミングより検索結果が変わることになる。

 
 

REPEATABLE READ

検索後に他のトランザクションによる変更がありコミットされても、その影響を受けな設定。
MyriaDBのデフォルトのトランザクション分離レベルになっている。

テーブルを初期状態に戻し以下の実行を行う。

① 【A】でトランザクション分離レベルをREPEATABLE READに設定して、テーブルを検索する。

 
 

② 【B】でREPEATABLE READに設定してテーブルを更新しコミットする。

 
 

③ 【A】で再度テーブルを検索する。

前回検索と同じ結果になり、ファジーリードは発生していない。

 
 

④ 【A】でトランザクションを終了させて再度検索する。

【B】で更新したplayer1のデータを参照できた。

 
 

⑤ 【A】でコイン数の合計値を検索する。

 
 

⑥ 【A】でテーブルにplayer3のデータを追加し、コミットする。

 
 

⑦ 【A】でコイン数の合計値を再度検索する。

同じ結果になった。ファントムリードも発生していない。

※ MriaDB(InnoDB)では、MVCC(MultiVersion Concurrency Control)という技術でファントムリードを防いでいる。

 
 

SERIALIZABLE

強制的にトランザクションを順序付けて処理する一番高いトランザクション分離レベル。
最も安全にデータを操作できるが、相対的に性能は低いため、めったに使われない。

テーブルを初期状態に戻す。

① Aでトランザクション分離レベルをSERIALIZABLEに設定して、テーブルを検索する。

② Bでテーブルにplayer3のデータを追加する。

ロック競合が起きてデータの追加が失敗した。

③ Bでテーブルを検索する。

問題なく実行した。

④ Aでトランザクションを終了させる。

⑤ Bでテーブルにplayer3のデータを追加する。

問題なく実行しました。
 
 

SERIALIZABLEの場合は読み取るすべての行に共有ロックをかける。そのため、ファントムリードもファジーリードも発生しない。
 
 

まとめ

トランザクションは1つの作業単位として扱う一連の操作の集まり。

トランザクション分離レベルはACID特性のI(Isolation、分離性)に関する概念で、他のトランザクションに影響を与えるレベルを意味する。
 
 
【READ UNCOMMITTED】
コミットされていない変更を他のトランザクションから参照できる。
ダーティリード、ファジーリード、ファントムリードが全て発生する。

 
 

【READ COMMITTED】
コミットされた変更を他のトランザクションから参照できる。
Oracle、PostgreSQL、SQL Serverのデフォルトのトランザクション分離レベルで、
ファジーリード、ファントムリードが発生する。
 
 

【REPEATABLE READ】
コミットされた追加・削除を他のトランザクションから参照できる
MySQLのデフォルトのトランザクション分離レベル。

ファントムリードが発生する。
ただしMriaDB(InnoDB)ではREPEATABLE READでもファントムリードが発生しない。
 
 

【SERIALIZABLE】
強制的にトランザクションを順序付けて処理する(直列化)。
読み取るすべての行に共有ロックをかける。
ダーティリード、ファジーリード、ファントムリードが全て発生しない。

-データベース基礎

© 2021 スタートダッシュ