複数のトランザクションを同時に実行しても矛盾が起きないよう制御する仕組み。
同時実行制御(並行制御)とは、複数のトランザクション(=ひとかたまりの処理)を同時に動かしても、結果に矛盾が起きないように順序を制御する仕組みのことです。データベースでは何千人もの利用者が同時に同じデータを読み書きするため、この制御が欠かせません。
身近な例で考えると、1つしかないトイレを複数人で使うのに似ています。鍵をかけずに2人が同時に入ろうとすれば混乱しますが、鍵で順番待ちをさせれば1人ずつ正しく使えます。同時実行制御はこの「鍵と順番」をデータに対して行うものです。
上のツールで▶ボタンを押すと、まず制御がない場合にT1とT2の更新が衝突して片方が消える様子を、シナリオを切り替えるとロックで正しく整理される様子を確認できます。
同時実行制御にはいくつかの方式があり、代表的なものは次のとおりです。
・ロック方式:使用中のデータに鍵(ロック)をかけ、ほかのトランザクションを順番待ちにさせる。最も基本的で広く使われる方式
・2相ロック方式:ロックの獲得段階と解放段階を分けることで、結果の正しさ(直列化可能性)を保証する
・MVCC(多版同時実行制御):データの「版(バージョン)」を複数持ち、読み取りは待たせずに古い版を見せる方式
ロック方式は分かりやすい反面、待ち時間が増えやすいという弱点があります。MVCC(=Multi-Version Concurrency Controlの略)は「読み取りは待たせない」ことで性能を上げる工夫です。多くのデータベース製品はこれらを組み合わせて使っています。
なぜ制御がないと矛盾が起きるのか、具体的に見てみましょう。残高1000円の口座に、T1が「+500円」、T2が「+200円」を同時に書き込むとします。
制御なしだと次のことが起きます。
・T1が「1000円」を読む
・T2も同じタイミングで「1000円」を読む(T1の書き込み前なので古い値)
・T1が「1000+500=1500円」を書き込む
・T2が「1000+200=1200円」を書き込んでT1の結果を上書きしてしまう
正しくは1700円になるべきところが、1200円になります。T1の「+500」が消えてしまいました。これを更新の喪失(ロストアップデート)と呼びます。原因は2つのトランザクションが同時に古い値を読んで、それぞれが独立に書き込んだことです。同時実行制御はこの「古い値を読んでしまう」問題を防ぐために存在します。
ロック方式では、使い方に応じて2種類のロックを使い分けます。
・共有ロック(読み取りロック):データを読むだけのときにかける鍵。ほかのトランザクションも同じデータを「読む」ことはできるが、「書く」ことはできない
・排他ロック(書き込みロック):データを書き換えるときにかける鍵。他のトランザクションは読むことも書くことも待たされる
なぜ2種類に分けるのか。読み取り同士は矛盾を起こさないからです。複数人が同じ本を同時に読んでも内容は変わりませんが、誰かが書き直している最中に別の人が読むと古い値を見てしまいます。だから「書くときだけ1人占め」にすることで、待ち時間を最小限に抑えながら矛盾を防ぎます。
身近な例で考えると、図書館の本に似ています。読むだけ(共有ロック)なら複数人が同時に借りられるですが、書き込む(排他ロック)なら1人が作業している間、他の人は待つ必要があります。
同時実行制御がないと、複数のトランザクションが互いの操作を踏みつぶし、次のような問題が起こります。
・更新の喪失(ロストアップデート):先に行った更新が、古い値での上書きによって消える(上のツールの例)
・ダーティリード:まだ確定していない(後で取り消されるかもしれない)データを読んでしまう
・非再現読み取り:同じデータを2回読んだのに値が変わってしまう
同時実行制御の目標は、並行で動かしても「1つずつ順番に実行したのと同じ正しい結果」になることです。これを直列化可能性と呼びます。複数人が同時に使う銀行・予約・在庫システムなどでデータの信頼性を守る、なくてはならない仕組みです。