データベース

DBMS複数利用時の問題とトランザクションの分離を学ぼう!(ダーティーリード、ファントムリード、READ COMMITTEDなど)

2020年2月15日

データベースにおいてはデータを正確に保つためにトランザクションを使うことでデータの原子性(処理を全て完了するか全て戻すか)を保っています。

つまり、データが中途半端な状態にならないように、「コミット」や「ロールバック」という仕組みを使っています。

「それなら安心だね!」って思うところですが、DBMSで「複数のトランザクションが同時実行」された場合、タイミングによっては正しい処理が行えないことが発生してしまいます。

今回はこのDBMSにおいて複数トランザクションを同時実行する際に発生する問題と「トランザクション分離」という対策に関して解説します!

 

DBMSにおける複数同時利用の問題とは?

DBMS上で複数トランザクションが同時に実行されると、タイミングによってはトランザクション毎に違うデータを取得したり、別の値で更新をしてしまうなど、データの不整合が発生してしまうことがあります。

このDBMSにおける複数同時利用時の問題として以下3つのケースが知られていますので、それぞれ細かく見ていきましょう!

と、その前に通常のDBMSへのほぼ同時処理の流れを掴んでおきましょう!

下の図にありますように、一つの商品の在庫数において、同時に二人の人から在庫を引き当てる(予約する)命令があったとします。ほぼ同時ですが、順番に処理がされるので、最終的な在庫数は意図した数になります。

DBMSへのほぼ同時のトランザクション処理

しかし、このような処理において、更新のタイミングやイレギュラーな処理の発生によって問題が起きてしまうことがあります。

ダーティーリードとは?

まだコミットされていない未確定の変更情報を他の人が読み取ってしまうことによって発生する問題のことをダーティーリード(dirty read)と呼びます。

下の図に例を載せてますが、在庫処理において、以下のような流れがダーティリードとなります。

ダーディーリードの流れ
  • Xさんが在庫から30個引いてデータを更新する(UPDATE)
  • Yさんが在庫から残りの在庫数を読み取り(SELECT)、そこから20個引いてデータを更新する(UPDATE)
  • Xさん側で問題が発生し30個引いた処理をキャンセルする
  • YさんはXさん側の問題を知らないので、20個引いたデータをそのまま確定する(COMMIT)
  • 本来はキャンセルした在庫数も考慮して、残り在庫数は80個なのに、データは50個になってしまう(間違ったデータの状態)

その後にキャンセルされてしまうかもしれない未確定の情報を読み取って、別の人が処理をし、確定させてしまうことで、データの食い違いが発生してしまうため、ダーティーリードは非常に危険な処理の流れになります。

 

ノンリピータブルリード(反復不能読み取り)とは?

あるテーブルに対して読み取り(SELECT)した後に、別の人がそのテーブルに対して更新(UPDATE)によってデータを書き換えてしまうと、次に読み取りした際に検索結果が異なってしまう問題のことをノンリピータブルリード(non-repeatable read)と呼びます。

データベースは複数の人が利用するので誰かがデータを読んだ後にデータが更新されることは当たり前なのですが、それによって不都合が起こる時があります。

下にその例を載せてみました。

ノンリピータブルリードの流れ
  • Yさんが在庫一覧のテーブルから全在庫数をSELECTで読み出す。その結果は在庫A+在庫B+在庫C=60個となる。
  • 次にYさんは在庫一覧テーブルから最大の在庫数をSELECTで読み出そうとするが、その前にXさんが在庫Bに80個追加するためのUPDATE(更新)を実施してしまう。
  • Yさんの最大の在庫数の商品をSELECTで読み出した結果は商品Bの100個となり、これは最初に読み込んだ全在庫数(60個)より多くなってしまう。

上記の例のように、通常全在庫数より最大の在庫数が多いことはあり得ませんが、処理の途中に他の人によってデータが更新されてしまうとデータの不整合が起こってしまうのです。

 

ファントムリードとは?

ファントムリード(phantom read)とは先ほどのノンリピータブルリードに似ています。最初のテーブル読み取り(SELECT)の後から2回目の読み取り(SELECT)の間に、別の人が行の追加(INSERT)を行うと、2回のSELECTで結果の行数が変わってしまうという問題です。

1回目の検索結果の行数を使った処理を行う時に、2回目の検索結果の行数が違うことで問題となってしまうことがありますね。

 

トランザクション分離による対策

ここまで説明してきましたDBMSにおける複数同時利用時の問題(ダーティーリード、ノンリピータブルリード、ファントムリード)を解決するためにDBMSには「トランザクションの分離性(isolation)」という制御があります。

この分離性の意味は、「DBMSがあるトランザクションを実行する時には他のトランザクションの異教を受けないよう分けて実行する」ということです。

DBMSはこれを実現するために「ロック」という仕組みを使います。ロックをかけるとその行は他の人のトランザクションからは読み書きができないようになります。これによりデータの一貫性が保たれます

分離による問題

トランザクションの分離によって、データの不整合が発生する問題を解決することができるので、何でも間でもロックすれば良いと思いますが、これにはデメリットもあります。

それは、ロックをかけると、その間は他のデータ処理が止まるので、データベースの動作が全体的に重くなってしまいます。一つ一つのロックは数ミリ秒なのですが、これが沢山発生すると大きな動作遅延に繋がってしまいます。

そのためにDBMSには分離レベルというものがあって、最適な設定を行うことができるようになっています。

分離レベルとは?

多くのDBMSでは、どこまでロックをかけるか、つまり先ほどのダーティーリードやファントムリードを防ぐか防がないかを使い分けることができるようになっています。

それをトランザクション分離レベルと呼びます。

下にトランザクション分離レベルの一覧を記載します。分離レベルによって、安全度と速度が変わることが分かるかと思います!

トランザクションの分離レベルの一覧

デフォルトでは分離レベル「READ COMMITTED」で動作していることが多いようです。READ COMMITTEDレベルはさほど厳しいロックではないため、ダーティリードしか防ぐことができませんが、その代わり速度を担保できるところが良いですね。

その他の分離レベルを利用したい場合はSQL文で「SET TRANSACTION ISOLATION LEVEL」という命令をすることで、任意の分離レベルにすることが可能です。

まとめ

今回はDBMSにおける複数同時利用時の問題とその対策としてのトランザクション分離に関して解説しました。

普段何気なく利用しているデータベースの情報ですが、データの更新や追加により、予期せぬデータ不整合が発生するリスクに対して、「トランザクションの分離」でリスクを減らしているのですね。

「ダーティリード」や「ファントムリード」など言葉からはなかなか想像できない仕組みでしたね(汗)

DBMSの製品(Oracleなど)によって分離レベルの一部しか使えないものもあるようなので、その辺も注意しておくと良いですね。

以上です!

 

-データベース