SQL
PostgreSQL
データベース
排他制御
新人プログラマ応援

新人に悲観ロックによる排他制御を体験してもらう

More than 1 year has passed since last update.

1. はじめに

新人に悲観ロックによる排他制御を説明する際、実際にPostgreSQLを操作して見せたデモの評判がよかったため、今回Qiitaの記事にしてみました。排他制御は並列実行のイメージが掴めないと知識だけで理解するのは難しいので、体験してみると多少は理解しやすくなるかと思います。

なお、排他制御の説明については、他の方の良い記事やWebサイトがあるのでそちらを参照ください。

2. 悲観ロックを試してみる

2.1. 事前準備

今回は「PostgreSQL 9.3」を利用して悲観ロックを試してみます。
PostgreSQLのインストールについては他の方の記事を参考にしてみてください。

  1. スタートメニューの「PostgreSQL 9.3」->「pgAdmin III」メニューをクリックし、pgAdminを起動します。
  2. pgAdminの「虫眼鏡(SQL)」メニューをクリックし、SQLを実行する画面を表示します。
  3. SQLを並行して実行するため、SQLの実行画面を2つ表示してください。
  4. 排他制御を実感するため、2つの画面は同時に確認できるように並べて表示してください。

2.2. ロック取得待ちによる悲観ロック

  1. 画面1、画面2の両方の「SQLエディタ」タブの左上の入力エリアに、以下のSQL文を入力します。
  2. 画面1、画面2の順に、画面上部の三角ボタンを押下し、入力したSQLを実行します。
ロック取得待ちになるSQL(例)
-- トランザクションの開始
begin transaction;
-- select for updateでロック取得待ちの排他制御を掛ける
select * from customer where customer_code = '00000001' for update;

今回の例ではcustomerテーブルとしていますが、1レコード以上のデータが格納されていれば、検索対象のテーブルはなんでも構いません。
必ず主キー検索で1レコードだけ選択される検索条件を設定し、for updateを付与してください。

ちなみにSQLでは--で始まる行がコメントで、PostgreSQLでは;がSQLの1文の終わりを意味します。

画面1 画面2
  1. 最初にSQLを実行した画面1にはすぐに取得レコードが表示されます。
  2. 二番目にSQLを実行した画面2はレコードのロック取得で待ち状態になります。
  3. 左下の状態が「クエリーの実行中。」、右下の処理時間が進んでいることからも実行中の待ち状態であることが分かるかと思います。
画面1 画面2
  1. 画面1の「SQLエディタ」タブの左上の入力エリアに、トランザクションをロールバック(終了)させるためのSQL文である「rollback;」を入力します。
  2. 画面1の画面上部の三角ボタンを押下し、入力したSQLを実行します。
  3. 画面1のトランザクションが終了してロックが解放され、画面2でロックが取得できた時点で、画面2に取得レコードが表示されます。
  4. この時点で画面2のクエリーが完了するため、左下の状態が「OK.」、右下に待ち時間も含めた処理時間が表示されます。画面の例では41450msで約40秒も待っていたことになります。
  5. これで検証は終わりです。画面2もロールバックしてください。
画面1 画面2

2.3. ロックが取得できない場合はエラーとする悲観ロック

  1. 画面1、画面2の両方の「SQLエディタ」タブの左上の入力エリアに、以下のSQL文を入力します。
  2. 画面1、画面2の順に、画面上部の三角ボタンを押下し、入力したSQLを実行します。
ロックが取得できない場合はエラーとなるSQL(例)
-- トランザクションの開始
begin transaction;
-- select for update nowait でロック取得でエラーの排他制御を掛ける
select * from customer where customer_code = '00000001' for update nowait;

前回との違いはfor updateの後ろにnowaitが付いている点です。英単語の意味の通り、ロック待ちを行わないという意味のオプションです。

画面1 画面2
  1. 最初にSQLを実行した画面1にはすぐに取得レコードが表示されます。
  2. 二番目にSQLを実行した画面2ではロックが取得できないためエラーになります。つまりnowaitを付けると、待ちではなくその場でエラーになるということです。
  3. これで検証は終わりです。画面1、画面2のどちらもロールバックしてください。
画面1 画面2

3. さいごに

今回はPostgreSQLを実際に操作し、悲観ロックによる排他制御を体験できるようにしてみました。悲観ロックがどういった動作なのか、nowaitの有無がどのように影響するのかが理解できたかと思います。
なお、今回試してはいませんが、排他制御を理解するには以下のパターンも試すことをお勧めします。

  • ロック取得中にfor updateの付いていないselect文を実行するとどうなるのか。
  • ロック取得中に別のレコードをselect for updateで検索するとどうなるのか。

PostgreSQLにはfor updateでロック待ちの時間を指定することはできませんが、Oracleなど他のデータベースでは待ち時間を指定することが可能なものも存在します。
また、レコードロックではなくテーブルロックで悲観ロックを実施すべき場面も出てくるかと思います。その場合も今回の検証と同様に、実際に試して動作を体験することをお勧めします。