共有ロック、占有ロック、デッドロック|データベースの排他制御

はじめに

トランザクション管理機能の1つである排他制御機能についてまとめました。

 

共有ロック、占有ロックの違い、

またデッドロックとその回避策について図解でまとめています。

 

この記事を読む前に

トランザクションの基礎的な部分は事前に理解しておく必要があるので、

必要であれば以下の記事をご覧ください

トランザクションのACID特性とトランザクション分離レベルが不十分な場合の挙動

 

 

排他制御を行わずに複数トランザクション同時実行すると発生する事象

複数のトランザクションを自由に同時に実行させると、

 

同じテーブルに対して、

同時に更新や参照を行ってしまい、

個々のトランザクションが他のトランザクションに影響を与え、

 

タイミングによっては誤った結果になってしまいます。

 

 

それを回避するためにDBMSでは、

並列実行されているトランザクションを直列実行可能(直列実行可能性)

になるように制御しています。

 

この時に行っている制御が排他制御です。

 

MEMO

直列実行可能性(serializable)
2つのトランザクションT1、T2が

T1→T2、T2→T1のどちらの順に実行されても

 

同じ結果になる場合、

直列実行可能性が保証されているといいます。

 

占有ロックと共有ロックの違いとは

排他制御は主に、データベースのロックによって実現しています。

 

ロックとは更新や参照する対象データに

鍵をかけて他のトランザクションから

アクセスできないようにすることです。

 

また、ロックには占有ロックと共有ロックの2種類があります。

 

共有ロックと占有ロックの違い

一般的にトランザクションがデータを参照する前に共有ロックを実施します。

一方、一般的にトランザクションがデータを更新する前に占有ロックを実施します。

 

共有ロックがかかっている場合、

他のトランザクションからは該当データに対して

共有ロックはかけれるが、占有ロックはかけれません。

 

つまり、

共有ロック中に、他のトランザクションから該当データの

参照はできますが、更新はできません。

 

占有ロックがかかっている場合、

他のトランザクションからは該当データに対して

共有ロック、占有ロック共にかけれない。

 

つまり、

占有ロック中に、他のトランザクションから該当データの

参照、更新はともにできません。

 

 

また、

ロック対象となるデータの単位をロックの粒度と言います。

 

粒度には、

行、テーブル、ページ、テーブル、データベースなどがあり、

例えばロックの粒度が行の場合トランザクションは

同時に同じテーブルを参照、更新することは可能です。

 

ロックの粒度がテーブルの場合には同じテーブルにアクセスするには

ロックが解除されるまで待つ必要があります。

 

 

デッドロックはどのように発生するのか??回避策は??

2つのトランザクションが

互いの処理に必要な資源をロックし合っているために、

処理が続行できなくなった状態のことをデッドロックと言います。

 

 

例えば上の例の場合、

在庫変更トランザクションは在庫テーブルのロック、

商品名変更トランザクションは商品テーブルのロック

がそれぞれ成功しています。

 

その上で、

在庫変更トランザクションはすでに(商品名変更トランザクションに)ロックされている

商品テーブルのロックが解放されるのを待ち、

商品名変更トランザクションはすでに(在庫変更トランザクションに)ロックされている

在庫テーブルのロックが解放されるのをお互い待つことになります。

 

このような状態がデッドロックです。

 

デッドロックになった場合、

大抵DBMSが一定時間ロック待ちになっているトランザクションを検出して

タイムアウトエラーで落としてくれます。

 

また、ここでは説明を省略しますが、

待ちグラフというものを利用してデッドロックを検出することもできます。

 

デッドロックを回避するには

デッドロックを回避するには、

各トランザクションの資源ロックの順番を同じにすれば良いです。

 

例えば先ほどの例の在庫変更トランザクションの資源ロックの順番を変更すると、

 

 

在庫変更トランザクションの資源ロックの順番を変更することで、

 

待ちが発生してもトランザクション完了によってロックが解放されるので

デッドロックを避けることができます。

 

あとは、、、

ロックの粒度を必要以上に大きくしないことです。

 

レコード単位のロックで良ければテーブル単位のロックにしないなど・・・

 

(レコード単位のロックをしようとしたけど、

意図せずインデックスが効いておらず、実はテーブルロックになってデッドロックが発生するという

恐ろしいことが実際の現場で見たことがあります)

 

 

まとめ

今回はトランザクション管理機能の排他制御について確認しました。

 

現場でシステム開発をする際、

利用しているDBMSのロックの粒度は行ロックなのか?

テーブルロックなのか?(意図した通り本当に行ロックになってますか??)

を調べてみたり、

ロック解放待ちの上限時間は何秒に設定しているのか

などは押さえておきましょう。

 

最後に、

デッドロックではないですが、、、

DBMSにデッドロックとみなされてエラーとなってしまったケースを体験したことがあるので紹介します。

 

あるユーザが一括更新処理実行中(処理に10分くらいかかる)に、

別のユーザが一括更新の処理対象のうちの1つのデータを更新しようとしてロック待ちとなり、

ロック解放待ち上限時間を超えてDBMSからエラーが返される。(DBMSは行ロック)

 

ということを実際に体験しました。

これが発生するたびに毎回アラートが上がって・・・って感じでした。

オンライン処理で10分もかかるような一括更新処理を許容している

システムにしてること自体に問題あるだろうと当時は諦めていましたが、、

 

ロック関連でのトラブルは意外と現場でよく発生するので、

共有ロック、占有ロック、デッドロックなどの基礎を押さえた上で、

現場でのトラブルに落ち着いて対処してください!!!

 

おまけ

今回の排他制御のように、

僕の9年間のSE・プログラマー生活の中で、

現場で必要なデータベース関連のまとめ記事を作ったので

是非そちらもご覧ください。

初めてデータベースを触る方に向けて〜新人プログラマー時代の自分に伝えたいこと〜

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください