LASSIC Media らしくメディア
トランザクション分離レベル|4段階と選び方
オンラインの在庫管理や口座残高のように、多くの利用者が同じデータへ一斉にアクセスするシステムでは、処理が同時に走ることが当たり前です。ところが、複数の処理が同じデータを読み書きするとき、互いの途中経過をどこまで見せるかを決めておかないと、数字が食い違ったり、二重に計上されたりといった不都合が起こります。この「どこまで見せるか」の度合いを段階で定めたものが、トランザクション分離レベルです。
分離レベルは、データベース設計のなかでも性能と正確さのバランスに直結する重要な設定です。厳しくすればデータの食い違いは抑えられますが、そのぶん同時にさばける処理は減ります。ゆるめれば速くなる代わりに、思わぬ不整合を招きかねないのです。この記事では、発注者やプロジェクトマネージャー、これから開発に関わる方に向けて、分離レベルの4段階と、並行実行で起きる不都合、選び方の勘所を整理していきます。
トランザクション分離レベルとは
トランザクションとは、いくつかの処理をひとまとまりにして、「全部成功か、全部なかったことにするか」のどちらかに扱う単位のことです。分離レベルは、このトランザクションが同時に複数動くとき、あるトランザクションの途中の変更を、他のトランザクションからどこまで見えるようにするかを定めた度合いを指します。
身近な例で考えてみましょう。ある担当者が商品の在庫数をまさに書き換えている最中に、別の担当者がその在庫数を読み取ったとします。書き換えが確定する前の中途半端な数を見せてよいのか、それとも確定するまでは古い数を見せるのか——。この見せ方の方針を段階で選べるようにしたのが分離レベルであり、SQLの標準では4つの段階が定められています。
なぜ段階が必要なのかというと、正確さと速さが相反するからです。他の処理の途中を一切見せないよう厳しくすれば、データの食い違いは防げますが、順番待ちが増えて同時にさばける量は落ちます。逆に見せる範囲を広げれば速くなりますが、不整合のリスクが上がります。どこで釣り合いを取るかを、システムの性質に応じて選ぶ——それが分離レベルの役割です。
この記事のポイント
- 分離レベルは、同時に動くトランザクションの途中経過をどこまで見せるかを定めた度合いです。
- SQLの標準では4段階あり、厳しくするほど不整合は防げますが、同時実行の性能は下がります。
- 扱うデータの正確さの要求と性能を見比べ、必要な範囲で段階を選ぶことが勘所です。
並行実行で起きる3つの不都合
分離レベルを理解するには、まず「見せすぎると何が起こるか」を押さえるのが近道です。並行して処理が走るときに生じる代表的な不都合は、次の3つに整理されます。
| 不都合 | 何が起こるか |
|---|---|
| ダーティリード | まだ確定していない(後で取り消されるかもしれない)変更を読んでしまう |
| 反復不能読み取り | 同じ行を二度読む間に、他の処理が値を書き換え、一度目と二度目で内容が変わる |
| ファントムリード | 同じ条件で二度検索する間に、他の処理が行を追加し、二度目に件数が増える |
いずれも、他のトランザクションの途中や結果が見えてしまうことで起こる食い違いです。たとえばダーティリードでは、後から取り消される数字をもとに判断してしまい、実在しない在庫を売ってしまうといった事態につながりかねません。どの不都合をどこまで許すのか——その線引きが、次に見る分離レベルの段階に対応しています。
4つの分離レベル
SQLの標準では、分離レベルを4段階で定めています。下にいくほど厳しくなり、先ほどの不都合を段階的に防げるようになるのです。ただし、厳しくするほど同時実行の性能は下がる、という関係にあります。
| 分離レベル | ダーティリード | 反復不能読み取り | ファントムリード |
|---|---|---|---|
| READ UNCOMMITTED | 起こりうる | 起こりうる | 起こりうる |
| READ COMMITTED | 防ぐ | 起こりうる | 起こりうる |
| REPEATABLE READ | 防ぐ | 防ぐ | 起こりうる |
| SERIALIZABLE | 防ぐ | 防ぐ | 防ぐ |
いちばんゆるい READ UNCOMMITTED は、確定前の変更まで見えてしまうため、3つの不都合すべてが起こりえます。一段階上げた READ COMMITTED は、確定した変更だけを読むようになり、ダーティリードを防ぐのです。さらに REPEATABLE READ では、いったん読んだ行が処理の途中で書き換わらなくなり、反復不能読み取りも防げます。そしてもっとも厳しい SERIALIZABLE は、複数のトランザクションをあたかも順番に一つずつ実行したかのように扱い、ファントムリードを含むすべての食い違いを防ぎます。下の図は、この段階と性能の関係を表したものです。
厳しいレベルほど手堅く見えますが、そのぶん処理は順番待ちしやすくなり、同時にさばける量は落ちます。だからこそ、常に最も厳しいレベルを選べばよいわけではなく、扱うデータの性質に応じた見極めが要るのです。
どう選ぶか(性能とのトレードオフ)
分離レベルの選定は、「どこまでの食い違いなら許せるか」と「どれだけの同時実行をさばきたいか」の兼ね合いで決まります。判断の手がかりを挙げておきます。
まず知っておきたいのは、多くのデータベース製品には、あらかじめ決められた初期のレベルがあるという点です。たとえばPostgreSQLは READ COMMITTED、MySQLのInnoDBは REPEATABLE READ を初期値としています。多くの一般的な処理は、この初期値のままで過不足なく動くように設計されています。そのため、まずは初期値を土台に置き、厳密な一致が要る処理に限って個別に引き上げる、という進め方が現実的です。
引き上げを検討したいのは、たとえば残高や在庫のように、読んだ値をもとに計算して書き戻す処理です。読み取りと書き込みの間に他の処理が割り込むと、二重に引き落としたり、在庫を売り越したりといった不整合につながりかねません。こうした箇所では、レベルを上げるか、あるいは後述するロックの仕組みを併せて用いることで、正確さを守ります。反対に、集計や参照が中心で、多少の値のずれが業務上問題にならない処理では、無理に厳しくして性能を落とす必要はないでしょう。
発注・開発でおさえる点
分離レベルは開発側の実装事項に見えますが、その判断の前提になるのは「どのデータに、どこまでの正確さが求められるか」という業務の要件です。発注や設計の段階で意識しておきたい点を挙げます。
厳密さが要る処理を洗い出す
お金や在庫のように、値の食い違いが直接損失につながるデータと、多少のずれが許容される参照系のデータとを、あらかじめ切り分けておきます。この仕分けが、どこで分離レベルを引き上げるべきかの判断材料になるのです。要件として整理しておくと、開発側との認識のずれを防げます。
ロックの仕組みとあわせて考える
同時実行の制御は、分離レベルだけで完結するものではありません。楽観ロックや悲観ロックといった仕組みと組み合わせて、必要な正確さを実現するのです。どちらか一方ではなく、両者を見比べて方針を決めることが、過不足のない設計につながります。
負荷を想定したテストを行う
分離レベルの影響は、多くの処理が同時に走る状況ではじめて表面化します。公開前に、想定される同時アクセスを模した負荷テストを行い、性能の落ち込みや不整合が起きないかを確かめておくと、公開後のトラブルを避けやすくなります。
よくある誤解と勘所
分離レベルは、言葉の印象と実際の働きがずれやすいテーマです。代表的なつまずきを表にまとめました。
| よくある誤解 | 実際のところ |
|---|---|
| いちばん厳しいレベルにすればよい | 同時実行の性能が落ちる。必要な箇所に限って引き上げるのが現実的 |
| 分離レベルを上げれば競合は起きない | 厳しくすると順番待ちが増え、デッドロックなどの別の問題も生じうる |
| 製品が違っても挙動は同じ | 初期値も同名レベルの実装も製品ごとに差があり、確認が要る |
| ACIDを守っていれば分離は考えなくてよい | 分離性はACIDの一要素。その度合いを具体的に選ぶのが分離レベル |
勘所をひとことでいえば、分離レベルは「正確さと速さのつまみ」だと捉えることです。厳しくするほど食い違いは減りますが、そのぶん速さを手放します。だからこそ、全体を一律に厳しくするのではなく、守るべきデータを見極めて、そこだけつまみを回す——という発想が、過不足のない設計につながっていきます。
まとめ
- 分離レベルは、同時に動くトランザクションの途中経過を、どこまで見せるかを定めた度合いである。
- 並行実行では、ダーティリード・反復不能読み取り・ファントムリードという不都合が起こりうる。
- SQLの標準では4段階あり、READ UNCOMMITTEDからSERIALIZABLEへ厳しくするほど不整合を防げる。
- 厳しくするほど同時実行の性能は下がるため、正確さと速さの兼ね合いで選ぶ。
- 製品ごとの初期値を土台に、厳密さが要る処理だけ引き上げ、ロックの仕組みとあわせて考える。
よくある質問
分離レベルとACIDの分離性は同じものですか。
深く関係していますが、指すものは異なります。ACIDの「分離性(Isolation)」は、トランザクションが互いに干渉せず独立して動くべきだという性質そのものを表します。一方、分離レベルは、その分離性をどの程度まで厳密に実現するかを段階で選べるようにしたものです。分離性という考え方があり、それを具体的にどこまで守るかを刻んだのが分離レベル、という関係で捉えると整理しやすくなります。
いちばん厳しいSERIALIZABLEにしておけば問題ないのではないですか。
正確さの面では最も強い一方、いつでも最適とは限りません。SERIALIZABLEは、複数の処理を順番に一つずつ実行したかのように扱うため、その分だけ順番待ちが増え、同時にさばける処理の量が落ちるのです。アクセスが集中するシステムでは、性能の低下が利用者の待ち時間として表れかねません。厳密さが本当に要る処理に絞って用い、それ以外は製品の初期値のまま動かす、という使い分けをおすすめします。
データベース製品によって挙動は変わりますか。
変わります。まず、初期の分離レベルが製品ごとに異なります。たとえばPostgreSQLはREAD COMMITTED、MySQLのInnoDBはREPEATABLE READが初期値です。加えて、同じ名前のレベルでも、内部での実現方法や細かな挙動に差がある場合があります。そのため、利用するデータベースの資料で初期値と挙動を確かめたうえで設計することが欠かせないのです。移行や乗り換えの際も、この違いに注意が要ります。
分離レベルとロックは、どちらを使えばよいのですか。
どちらか一方を選ぶというより、組み合わせて使うものと捉えるのが実務的です。分離レベルは、トランザクション全体で「どこまで他の処理の影響を見せるか」の土台を決めます。一方でロックは、特定のデータに対して個別に更新の競合を制御する仕組みです。全体の方針を分離レベルで定めつつ、とくに競合が起きやすい箇所で楽観ロックや悲観ロックを用いる、という重ね方が、正確さと性能の両立につながります。
著者:テレリモ総研編集部 鈴木 亮佑
LASSICでは、国内ニアショア開発体制を活かし、データベース設計や同時実行制御の方針づくりから実装、公開済みシステムの性能・不整合の切り分けまでを一貫して支援する体制です。要件定義の段階から実装、テスト、リリース後の運用・保守まで、工程を分けずに任せられる点も強みでしょう。分離レベルやロックの設計でお困りの際も、ご相談いただけます。
ご不明な点はお問い合わせフォームからもご連絡いただけます。