データベース入門:トランザクション分離レベルとは
「トランザクション」および、忘れがちな「ACID特性」「トランザクション分離レベル」について、順を追って説明していきます。
トランザクションとは
データベースで操作する複数の処理を1つの単位でまとめたものです。
データ不整合が起こらないよう、データの追加・更新・削除処理で利用されます。
それでは具体的に、アプリケーションでガチャを引く人を例にとって考えてみましょう。
ガチャを引く際のデータベース処理は、シンプルに考えて「手持ち有償石の消費」「アイテムの付与」の2つの処理を必要とします。
これらをトランザクションを利用することで、1つの単位でまとめることができます。
もし万が一アイテムの付与処理でエラーとなってしまった場合でも、トランザクションが張っていればトランザクション単位でロールバックできますので、アイテムは付与されなかったのに有償石だけが消費されてしまった、といった事態を防ぐことができます。
ガチャ処理でトランザクションを張っている場合
ガチャ処理でトランザクションを張っていない場合
ACID特性とは
トランザクションが持つべきデータの信頼性と整合性を定義している概念です。
ACID特性を知ることで、トランザクションで信頼性と整合性を高めるために何を保証すべきかを理解することができます。
また、ACID特性を持ったトランザクションは「ACIDトランザクション」と呼ぶこともできます。
ACIDトランザクションは、データの信頼性の整合性を最大限に高めることができます。
Atomicity(原子性)
トランザクションは複数の処理(登録、読出、更新、削除)を1つの単位としてまとめたもので、それらが全て実行されるか、あるいは全く実行されないかのいずれかを保証します。
複数の処理で1つでもエラーになった場合には、全てロールバックされます。
Consistency(一貫性)
トランザクションの実行前後で、データに矛盾がなく整合性が保たれることを保証します。
設定されている整合性制約は全て満たさなければいけません。
Isolation(分離性・独立性)
複数のトランザクションが同時に実行されても、それぞれのトランザクションで独立して処理されることを保証します。
他のトランザクションに影響を与えたり、逆に他のトランザクションから影響を受けたりしないようにします。
Durability(永続性)
正常に完了したトランザクションの結果は、永続的に保存されることを保証します。
システム障害が発生しても、データが失われることはありません。
トランザクション分離レベルとは
トランザクションが複数同時に張られている場合に、どの程度の一貫性・分離性を保証するかをレベル定義したものです。
前述しました通り、ACID特性を意識したトランザクション処理を行うことは、データの信頼性と整合性を高めるためには非常に大事なことです。
ACID特性だけを優先するのであれば、分離レベルは「SERIALIZABLE」を選ぶべきでしょう。
ただし、残念ながら堅牢にすればするほどパフォーマンスを犠牲にしなければいけません。一貫性・分離性の保証とパフォーマンスはトレードオフなのです。
ですので、どの程度までは保証して、どの程度は許容できるのか、データベースの用途に応じて適切な分離レベルを選択する必要があります。
1.ダーティリード
他のトランザクションでコミットされていない更新が参照されてしまう現象です。
分離レベルがREAD UNCOMMITTED(コミットされていない読み取り)の時に発生します。
2. ファジーリード
他のトランザクションでコミットされた更新が参照されてしまう現象です。
分離レベルがREAD COMMITTED(コミットされた読み取り)以上の時に発生します。
3. ファントムリード
他のトランザクションでコミットされた追加・削除の結果が参照されてしまう現象です。
分離レベルがREAD COMMITTED(コミットされた読み取り)以上の時に発生します。
4. ロストアップデート
更新したはずのデータが、他のトランザクションでも更新されることで先行した更新が失われてしまう現象です。
分離レベルがREPEATABLE READ(繰り返し可能な読み取り)以上の時に発生します。
適切なトランザクション分離レベルは?
もしパフォーマンスを犠牲にして「SERIALIZABLE」を選択したとしても、トランザクション分離レベルだけでは多数のトランザクションを適切に共有・排他管理できる訳ではありません。
アプリケーション側でも、トランザクションを適切に共有・排他管理できる設計である必要があります。
データベースの用途によりますので一概には言えませんが、
個人的にはパフォーマンスを意識した「READ COMMITED」「REPEATABLE READ」のいずれかを選択し、それによって発生する現象についてはアプリケーション側でデータの正確性を担保するのが良いと考えています。
発生するロストアップデート等の現象は、占有ロック(FOR UPDATE)と共有ロック(FOR SHARE(MySQL8)/LOCK IN SHARE MODE(MySQL5.7以前))を活用することで回避することができます。
占有ロック
レコードの更新と参照を禁止する際に利用するロックです。
参照先のレコードを独占できます。
共有ロック
レコードの更新を禁止する際に利用するロックです。
参照先のレコードの書き換えのみを防げます。
参考
Discussion