DBアタマアカデミー

第2回 トランザクションを知ればデータベースがわかる―「データ復旧」「同時実行制御」を行う“不完全な”しくみ(3)

この記事を読むのに必要な時間:およそ 4 分

トランザクションと同時実行制御

ここからは,トランザクションの2つ目の重要な機能である「同時実行制御」について見ていきます。

私たちがデータベースを利用するとき,1人で占有しているという贅沢なことはまずありません。自分以外にも多くの人が,検索,更新,削除といった多様な処理を同時並行で実行しています。しかし,私たちはそのことを意識しません。ユーザが「今は誰それが私と同じテーブルを更新しているから,この処理は後でやろう」と考えなければいけないようなシステムは,実用に堪えないでしょう。せいぜいあるとすれば,混みあったときにパフォーマンスが悪くなることを懸念して作業をずらす配慮をするぐらいです。

私たちがこのように,データベースを使うに際して他人の存在に無頓着でいられるのは,DBMSがうまい具合に複数のユーザの処理をスケジューリングし,結果の整合性を担保してくれているからです。この性質をACIDのIIsolation:分離性または独立性)と呼びます。

本節では,DBMSがどうやって分離性を保証しているのか,そのしくみを詳しく見ていくことにしましょう。そこでまずキーワードとして登場するのが,Serializabilityという概念です。日本語では,直列化可能性とか逐次化可能性と呼びます。

直列させれば分離性の担保ができる

硬い言葉ですが,その定義は別に難しくありません。今,たとえばTa,Tb,Tcという3つの同時実行されているトランザクションがあったとしましょう。これらの結果が(更新に限らず検索の結果も)「正しい」ことは,どういう場合に言えるのでしょうか。

これに対する答えは,「3つのトランザクションが順次実行された場合と同じ結果が得られる場合だ」というものです。要するに,並行で実行されるケースを考えるから話がややこしくなるわけで,そもそも並行でない(=直列に)実行される場合の結果を考えて,それと同じならOK,ということです。直列実行とは,自分のトランザクションの裏側にほかのトランザクションがいないケースですから,これ以上ない厳格な定義です。DBMSが何らかの方法で,並行実行されているトランザクション群にこの性質を担保できれば,「正しい結果」を常に保証できる,ということが言えるわけです図4)。

図4 直列に実行された場合と結果が同じなら結果の正しさを担保できる

図4 直列に実行された場合と結果が同じなら結果の正しさを担保できる

図4 直列に実行された場合と結果が同じなら結果の正しさを担保できる

どうすれば直列させられるの?

それでは,並行実行されているトランザクション群を直列にするためには,具体的にどんな方法を使えばよいのでしょう。

真っ先に考えつく単純な方法は,「本当にトランザクションをシーケンシャルに実行するようスケジューリングする」というものです。Taが最初に開始されていたら,終わる前にTbが始まったとしても,待機させます。同様に,Tcも待たせます。こうすると,DBMSはある瞬間においては常に1つのトランザクションしか実行されていないことになります。これは必ず直列させられる方法ですが,これを同時実行制御と呼ぶのは,同時実行してないのですから「看板に偽りあり」です。

それでも,この方法が実用的なら使って悪いことはないのですが,実際はこの荒っぽいやり方は,多くの場合にトレードオフを許容できないぐらいのパフォーマンス低下(スループットおよびレスポンスタイムの悪化)を引き起こしてしまいます。

ではどうすればよいか? そこでDBMSが取り入れた方法が,ロックによる解決です。

ロックによる解決

ロックとは,「鍵」という名前のとおり,ある資源に対してほかのユーザが使用できないよう鍵をかけることです。データベースにおける資源とは,テーブル,インデックス,シーケンス,ビューなどオブジェクト全般が該当します。

ロックの種類には一般に共有ロック(Sロック)と排他ロック(Xロック)の2つがあり,SはShared,XはeXcludedの略です。それぞれ読み込みロック/書き込みロックとも呼ばれます。

排他ロックのほうはイメージしやすいものです。テーブルのある行を更新しようと思えば,その行に対するほかのトランザクションのアクセスを一切禁止する必要があります。一方,共有ロックのほうは,ほかの共有ロックと両立するという特性を持っています。これは「ロック」という言葉の意味からは矛盾しているように感じられますが,共有ロックは読み取り(SELECT)の対象にかけられるものであるため,ほかのSELECTを禁止する必要がないからです。だから,共有ロックといえども,排他ロックとは両立することはできないのです。

表2は,ある資源XにトランザクションAが先にロックをかけていて,トランザクションBがあとからロックを取得しようとした場合の両立可能性を示すマトリクスです。両立可能なのは,共有ロック同士のみ,ということがわかります。

このように,使う資源を必要レベルに応じて占有/共有する,という方法で,DBMSは直列可能性を担保できます。しかし,このロック方式もまた,無視できない問題を2つ抱えています。

表2 共有ロック(S)と排他ロック(X)の両立可能性

 
A
XS
BX××
S×

ロックのコスト:スラッシングとデッドロック

スラッシング

DBMSがロックを行うとき,具体的にはロックの取得と解放という2つの動作をしています。そして,ロックが取得されている資源にほかのトランザクションがアクセスをかけてきたら,共有ロック同士でない限り「ブロック」を行います。あとから来たトランザクションをロック解放まで待たせるわけです。よく駅や空港のトイレで行列ができるのを見かけますが,あれなどまさにブロックされたトランザクション群の典型です注5)。トイレの便座を共有ロックで,というわけにはいきませんね。

このしくみから必然的に導かれる結果は,並行トランザクション数が一定数を超えると,1つのトランザクションが待機させられる頻度と時間が増え,平均のパフォーマンスが悪くなるということです。システムの特性(更新が多いのか,検索が多いのか)やハードウェア性能にも左右されるため,一般的な閾値(いきち)は負荷試験をやって測るしかありませんが,このようにロックによるパフォーマンス低下が起きる現象をスラッシングthrashingと呼びます。

一度スラッシングが発生するところまでトランザクションの多重度が上がると,それ以上の多重度では性能は劣化する一方となります図5)。そのため,対策としては限界多重度を超えない程度に流量制限を行うか,ロック粒度を小さくするなどアプリケーションロジックの見直しを行うか,などを考えなければなりません。

図5 ロックによるスラッシングの発生

図5 ロックによるスラッシングの発生

『Database Management Systems 3rd ed.』P.534の図を一部日本語訳

デッドロック

他方デッドロックは,スラッシングのような程度の問題とは異なり,論理的な問題であり,それだけに発生条件も厳密です。簡単に言えば,複数のトランザクションが複数の資源をロックする場合(今は単純化のため,2つとしましょう),互いに相手の資源解放を待つ状態となり,永遠に待機する閉路に陥ることです図6)。

図6 デッドロック

図6 デッドロック

  • Aが資源Xを排他ロックする
  • Bが資源Yを排他ロックする
  • Aが資源Yを排他ロックしようとするが,Bの先行ロックにより待機状態となる
  • Bが資源Xを排他ロックしようとするが,Aの先行ロックにより待機状態となる

このデッドロックを解消する方法は,どちらか一方のロックを強制的に解放してやることです。デッドロック検知のしくみを持っているDBMS注6もありますし,またトランザクションに待機の上限時間を設定することで,長時間待機しているトランザクションを破棄(アボート)させるという方法もあります。

アプリケーションの作成側としてデッドロックを発生させないため注意することは,論理的に図6のような閉路を作らないよう設計することはもちろん,なるべく不要なロックを取得しない,ロックの粒度を可能な限り小さくする,といった方針に従うことが必要となります。

注5)
このトイレの便座のように,多数のトランザクションからロックを要求される人気のオブジェクトのことをホットスポットhot spotと呼びます。ホットスポットはこのあと解説するスラッシングの発生現場となります。
注6)
たとえばOracleは,デッドロックを自動的に検出し,そのデッドロックに関係する文の一方をロールバックする機能を持っています。

著者プロフィール

ミック

SI企業に勤務するDBエンジニア。主にデータウェアハウス業務に従事している。自身のサイト「リレーショナル・データベースの世界」でデータベースとSQLについての技術情報を公開している。『Web+DB Press』で「SQLアタマアカデミー」を連載中。

著書:『SQL ゼロからはじめるデータベース操作』(翔泳社,2010)『達人に学ぶ SQL徹底指南書』(翔泳社,2008)訳書:J.セルコ『SQLパズル 第2版』(翔泳社,2007)

DBアタマアカデミー:サポートページ

コメント

コメントの記入