排他制御の理解と アプリケーションへの適用

THE
Database
FOR Network Computing™
排他制御の理解と
アプリケーションへの適用
日本オラクル株式会社
はじめに
マルチユーザーアプリケーションを設計するうえで、排他制御は最も重要なことの1つです。
排他制御の設計次第ではデータの整合性が失われたり、ロック待ちが頻繁に発生したりする可能
性があります。
Oracle のようなマルチユーザー用データベースは、複数ユーザーのトランザクションを正しく
処理するための「排他制御機能」を持っています。たとえば更新中の行には排他ロックがかかっ
ています。そして処理が終わるまでは、ほかのユーザーはロックされている行を更新できません。
しかし実際のシステムでは、Oracle の自動的な排他制御だけでは不十分です。アプリケーション
側でも排他制御を考慮する必要があります。
本書では、マルチユーザーアプリケーションを作成するために必要な次のことを解説します。
l
マルチユーザー環境における Oracle の動作
l
Oracle の読み取り一貫性
l
Oracle のロック管理
l
アプリケーションにおけるロック戦略
l
ロック戦略のアプリケーションへの適用
アプリケーション開発ツールに依存することは記述していません。純粋に Oracle を使用したと
きの排他制御方法を解説します。そのため、自動的に排他制御を行う開発ツール(Oracle Object
for OLE や Oracle Developer)では、本書で解説しているとおりには直接適用できないこともあり
ます。しかし、本書で解説していることは開発ツールに依存しない普遍的な考え方です。本書の
内容を理解すれば、あらゆる開発ツールに応用できます。
対象の読者としては、基本的な SQL やトランザクションの概念を理解しているアプリケーショ
ン設計者や開発者を想定しています。
なお本書では Oracle8 Enterprise Edition R8.0.5 for Windows NT および Oracle8 Enterprise
Edition R8.0.5 for Solaris を使用しています。Oracle8 をベースとしていますが、その多くの内容
は Oracle7 でも有効です。
※
Oracle,Oracle7,Oracle8 はオラクル社の登録商標です。
※
その他のすべての企業名と商品名は各社の登録商標または商標、製品名です。
※
なお、本文中では™・®は明記していません。
Ⅰ
目次
1. トランザクション管理の基本............................................................ 1
1.1. トランザクションのコミットとロールバック..........................................1
1.2. セーブポイントを使う ..........................................................................2
2. 複数ユーザー間のデータの見え方..................................................... 4
2.1. データを変更したときのデータの見え方 ................................................4
2.2. 文レベルの読み取り一貫性とは .............................................................6
2.3. トランザクションレベルの読み取り一貫性とは ......................................7
2.3.1. 読み取り専用トランザクションを使う......................................................7
2.3.2. シリアライザブルトランザクションを使う ...............................................9
3. Oracle におけるロック管理の理解.....................................................11
3.1. 暗黙的にロックする ........................................................................... 11
3.2. 明示的にロックする ........................................................................... 12
3.2.1. 明示的に表ロックを獲得する.................................................................. 13
3.2.2. 明示的に行ロックを獲得する.................................................................. 14
3.2.3. 明示的行ロックの動作 ............................................................................ 16
3.3. 参照整合性制約を定義したときの暗黙的ロック .................................... 17
3.4. デッドロックとは .............................................................................. 20
3.4.1. デッドロックの検出 ............................................................................... 21
3.4.2. デッドロックの回避 ............................................................................... 22
3.5. ロックを強制的に解除する ................................................................. 22
3.5.1. セッションを強制終了する ..................................................................... 23
3.5.2. EXPIRE_TIME を使う............................................................................ 24
3.5.3. プロファイルを使う ............................................................................... 24
4. マルチユーザーアプリケーションのロック戦略 ................................25
4.1. 悲観的ロック(Pessimistic Lock)とは ...................................................... 26
4.2. 楽観的ロック(Optimistic Lock)とは....................................................... 26
5. ロック戦略を Oracle に適用する ......................................................27
5.1. 悲観的ロックを Oracle に適用する ....................................................... 27
5.2. 楽観的ロックを Oracle に適用する ....................................................... 28
6. ロック戦略をアプリケーションに適用する.......................................30
6.1. 1画面に1行のデータを表示する........................................................ 30
6.2. 1画面に複数行のデータを表示する..................................................... 31
6.3. どれを選択するべきか?..................................................................... 31
7. 参考文献.........................................................................................33
Ⅱ
1.トランザクション管理の基本
はじめにトランザクションの復習をします。
トランザクションとは、密接に関連していて切り離すことのできない、一連のデータベース操作
のことです。Oracle では、前回 COMMIT もしくは ROLLBACK を実行してから、次に COMMIT
もしくは ROLLBACK を実行するまでの操作が1つのトランザクションです。トランザクション
は自動的に開始されます(暗黙的トランザクションの開始)。
SELECT * FROM emp WHERE empno=7369;
これで1つの
UPDATE emp SET JOB='CLERK' WHERE empno=7369;
トランザクション。
DELETE FROM emp WHERE empno=7900;
COMMIT;
図 1 Oracle のトランザクション
1.1.トランザクションのコミットとロールバック
トランザクションを制御するには、COMMIT コマンドと ROLLBACK コマンドを使用します。
それぞれの機能は次のとおりです。
COMMIT
現行トランザクションで加えた変更を確定し、このトランザクションを終了します。このとき
トランザクション内のセーブポイントはすべて消去され、このトランザクションによるロックも
すべて解放されます。セーブポイントについては、のちほど説明します。
次の例では、社員番号 7499 の従業員データを emp 表から削除し、確定しています。
SQL> DELETE FROM emp WHERE empno=7499
1 行が削除されました。
SQL> COMMIT;
コミットが完了しました。
ROLLBACK
現行トランザクションで加えた変更をすべて取り消します。
次の例では、社員番号 7521 の従業員データを emp 表から削除し、それからその変更を取り消
しています。
SQL> DELETE FROM emp1 WHERE empno=7521;
1 行が削除されました。
SQL> ROLLBACK;
ロールバックが完了しました。
SQL> SELECT ename FROM emp WHERE empno=7521;
ENAME
--------WARD
1 排他制御の理解とアプリケーションへの適用
Oracle では、明示的に COMMIT もしくは ROLLBACK を発行することが基本です。しかし次
の場合には、トランザクションは自動的に終了します。
l
DDL 文(CREATE、DROP、RENAME、ALTER など)を発行したとき。DDL 文を発行
すると、その前後で暗黙的に COMMIT が発行されます。つまり未コミットのデータがあ
るときに DDL 文を発行すると、そのトランザクションは自動的にコミットされます。
l
Oracle との接続を切断したとき(現行トランザクションはコミットされます)。
l
ユーザープロセスの異常終了を検知したとき(現行トランザクションはロールバックされ
ます)。
重要
CREATE や DROP、ALTER などの DDL 文はトランザクションの対象になりません。したがってテー
ブルを削除しても、ROLLBACK では元に戻りません。
1.2.セーブポイントを使う
Oracle には COMMIT と ROLLBACK 以外にも、SAVEPOINT というコマンドがあります。
SAVEPOINT コマンドを使うと、トランザクションの途中にセーブポイントという中間ポイント
を定義できます。そして定義したセーブポイントまで(つまりトランザクションの一部)のトラン
ザクションをロールバックできます。
セーブポイントはストアドプロシージャにも有効です。各プロシージャを開始する前にセーブ
ポイントを設定しておけば、プロシージャがエラーとなっても、容易にデータを元の状態に戻せ
ます。
次の例では、p1 というセーブポイントを定義しています。
SAVEPOINT p1;
セーブポイント名は、スキーマオブジェクトの命名規則に準じます。同じ名前のセーブポイン
トを定義すると、最初に定義したほうが無効になります。セーブポイントまでロールバックする
ためには、セーブポイントを指定した ROLLBACK コマンドを使います。
次の図はセーブポイントの概念です。
トランザクション開始
ROLLBACK;
UPDATE
SAVEPOINT A
ROLLBACK TO A;
UPDATE
SAVEPOINT B
ROLLBACK TO B;
DELETE
現在の位置
図 2 セーブポイントの概念図
この図では一度しかロールバックしていませんが、次のように2段階でロールバックすること
も可能です。
排他制御の理解とアプリケーションへの適用 2
ROLLBACK TO B;
ROLLBACK TO A;
ただし次のように一度ロールバックしたものを再び戻すことはできません。
ROLLBACK TO A;
ROLLBACK TO B;
セーブポイントを指定した ROLLBACK コマンドによって、次の処理が行われます。
1.指定したセーブポイント以降のトランザクションをロールバックします。
2.指定したセーブポイントよりも、あとに定義したセーブポイントはすべて削除します。したが
って、指定したセーブポイントまでは繰り返しロールバックできます。
3.指定したセーブポイント以降に獲得した表と行のロックを解放します。
実際の例を紹介します。セーブポイントAとBを定義します。
SQL> UPDATE emp SET sal=1000 WHERE empno=7369;
1 行が更新されました。
①
SQL> SAVEPOINT a;
セーブ・ポイントが作成されました。
SQL> UPDATE emp SET sal=2000 WHERE empno=7449;
1 行が更新されました。
②
SQL> SAVEPOINT b;
セーブ・ポイントが作成されました。
SQL> DELETE FROM emp WHERE empno=7844;
1 行が削除されました。
③
セーブポイントBまでロールバックします。このとき③の DELETE 文だけがロールバックされ
ます。セーブポイントBはまだ残っています。
SQL> ROLLBACK TO b;
ロールバックが完了しました。
SQL> SELECT ename FROM emp WHERE empno=7844;
ENAME
---------TURNER
次にセーブポイントAまでロールバックします。②の UPDATE 文がロールバックされ、セー
ブポイントBは削除されます。
SQL> ROLLBACK TO a;
ロールバックが完了しました。
次にセーブポイントBに戻ろうとしても既に削除されているので、次のエラーが発生します。
SQL> ROLLBACK TO b;
エラー行: 1: エラーが発生しました。
ORA-01086: セーブポイント: B は設定されていません。
3 排他制御の理解とアプリケーションへの適用
2.複数ユーザー間のデータの見え方
Oracle は、複数のユーザーで使用することを目的としたデータベースです。今までシングルユ
ーザー用のデータベースやファイルベースのデータベース(ISAM)しか使ったことのない人にとっ
て、いろいろ疑問に思う点があるかもしれません。この章ではマルチユーザーデーターベースの
基本であるセッションの独立性、つまりデータを変更したときのデータの見え方について、例を
あげて解説します。
そして読み取り一貫性についても解説します。読み取り一貫性は、行レベルロックと並ぶ
Oracle の基本機能です。デフォルトの文レベルだけでなく、トランザクションレベルで施行する
こともできます。
重要
トランザクションは、マシンレベルではなくセッションレベルで独立です。つまり同一のコンピュ
ータから複数のセッションを張っても、複数のコンピュータから単一のセッションを張っても、
Oracle から見ればどちらも同じです。
2.1.データを変更したときのデータの見え方
2つの独立したセッション間におけるデータの見え方を解説します。
更新のとき
Aさん
Bさん
1 SELECT empno,ename,job FROM emp
WHERE empno=7369;
EMPNO ENAME
WHERE empno=7369;
JOB
--------- ---------- --------7369 SMITH
SELECT empno,ename,job FROM emp
EMPNO ENAME
JOB
--------- ---------- ---------
CLERK
7369 SMITH
CLERK
2 UPDATE emp SET job='SALESMAN'
WHERE empno=7369;
1 行が更新されました。
3
SELECT empno,ename,job FROM emp
WHERE empno=7369;
EMPNO ENAME
JOB
--------- ---------- --------7369 SMITH
CLERK
4 COMMIT;
5
SELECT empno,ename,job FROM emp
WHERE empno=7369;
EMPNO ENAME
JOB
--------- ---------- --------7369 SMITH
SALESMAN
1.はじめはAさんもBさんも同じデータが見えます。
2.Aさんは社員番号 7369 の社員の職種を変更します。
3.この変更はBさんから見えません。
5.Aさんが COMMIT して、はじめて見えるようになりました。
排他制御の理解とアプリケーションへの適用 4
削除のとき
Aさん
Bさん
1 DELETE FROM emp WHERE empno=7934;
1 行が削除されました。
2
SELECT empno,ename,job FROM emp
WHERE empno=7334;
EMPNO ENAME
JOB
--------- ---------- --------7934 MILLER
CLERK
3 COMMIT;
4
SELECT empno,ename,job FROM emp
WHERE empno=7334;
レコードが選択されませんでした。
1.Aさんは社員番号 7934 の社員データを削除します。
2.Bさんは、この削除された行をまだ見ることができます。
4.Aさんが COMMIT して、はじめて削除されたことがわかりました。
挿入のとき
Aさん
Bさん
1 INSERT INTO emp
VALUES(8000,'TESTUSER','SALESMAN',7698,'
98-04-01',700,100,30);
1 行が作成されました。
2
SELECT empno,ename,job FROM emp
WHERE empno=8000;
レコードが選択されませんでした。
3 COMMIT;
4
SELECT empno,ename,job FROM emp
WHERE empno=8000;
EMPNO ENAME
JOB
--------- ---------- --------8000 TESTUSER
SALESMAN
1.Aさんは新しく一行追加します。
2.この追加された行はBさんから見えません。
4.Aさんが COMMIT して、はじめてBさんから見えるようになりました。
まとめ
上記の結果から、次のことがわかります。
l
トランザクションをコミットするまで、ほかのユーザーはその変更結果を知ることはでき
ません。
l
暗黙的にトランザクションは開始され、明示的に COMMIT もしくは ROLLBACK を実行
しない限りトランザクションは終了しません。
他社データベースの中には、UPDATE や INSERT を実行すると、すぐにほかのユーザーから
見えるようになるものもあります。このようなデータベースではトランザクションの開始を明示
的に指定しない限り、トランザクションは開始されません(明示的トランザクションの開始)。別の
言い方をすれば、SQL の実行毎に自動的に COMMIT されます。
5 排他制御の理解とアプリケーションへの適用
2.2.文レベルの読み取り一貫性とは
読みとり一貫性とは、ユーザーが問い合わせを発行した時点のデータを読み取ることを保証す
る機能です。Oracle は、デフォルトで文レベルの読み取り一貫性を施行します。
まず文レベルの読み取り一貫性について解説します。
読み取り一貫性あり
読み取り一貫性なし
1:00 A さん読取開始
変更1
¥100→¥200
変更1
¥100→¥200
変更2
¥100→¥200
2:00 A さんが半分読
んだ時点で B さん
が更新
¥200
変更2
変更前
¥100
3:00 A さん読取終了
A さんは B さんによる変更の影響
B さんの変更2だけを読んでし
を受けない
まい不整合が生じる
図 3 読み取り一貫性
借方・貸方に100円の仕訳が1行づつある伝票があります。
1.Aさんは帳票を出力するために、この伝票データの読み取りを開始します。
2.次に 2:00 の時点でBさんは、この伝票の借方・貸方を¥100→¥200 に変更します。(変更1:
借方、変更2:貸方)
[左図]読み取り一貫性のあるデータベースのとき
Aさんは 2:00 の時点では既に変更前の借方¥100 を読んでいます。そして変更後の貸方¥200(変
更 2)を読もうとした時点で「この行は問い合わせ開始後に変更された行である」と自動的に判断
し、変更前の貸方¥100 を読みます。つまり Oracle の問い合わせは、問い合わせ開始後に変更さ
れた結果には一切影響を受けません。
[右図]読みとり一貫性を持たないデータベースのとき
Aさんは変更前の貸方¥100 を読んでいながら、変更後の貸方¥200(変更 2)を読んでしまい、左
右がバランスしなくなります。つまり、このBさんのトランザクションが2箇所の変更から構成
されているにもかかわらず、Aさんはその一方だけを読むことによって問い合わせ結果の不整合
が発生します。これをダーティーリードといいます。
「スナップショットが古すぎます」について
Oracle は、ロールバックセグメントを利用して読み取り一貫性を施行します。そのため大規模
な問い合わせでは、ごくまれに一貫した結果セットを戻せないことがあります。原因は、読み取
り一貫性を施行するために必要なデータがロールバックセグメントに残っていないためです。こ
のときには次のエラーが発生します。
ORA-01555: スナップショットが古すぎます。
このエラーが発生したときは、ロールバックセグメントのサイズや数を増やすことによって回
避できます。また問い合わせを実行する前に表全体を共有ロックして、変更処理を禁止すること
によっても回避できます。
排他制御の理解とアプリケーションへの適用 6
2.3.トランザクションレベルの読み取り一貫性とは
「文レベル」では、問い合わせ発行時点におけるデータの一貫性を保証するのに対し、「トラ
ンザクションレベル」では、トランザクション開始時点におけるデータの一貫性を保証します。
たとえば PM5:00 にトランザクションを開始すると、それ以降に複数の問い合わせが発行されて
も、PM5:00 におけるデータを繰り返し読むことができます。
ほとんどの場合、文レベルの読み取り一貫性で間に合うでしょう。しかしアプリケーションの
種類や処理内容によっては、トランザクションレベルの読み取り一貫性が必要になることもあり
ます。トランザクションレベルの読み取り一貫性を施行するには、次の2つの方法があります。
ヒント
l
読み取り専用トランザクションにする。
l
アイソレーションレベルを SERIALIZABLE に設定する。
アイソレーションレベルの設定は Oracle7 R7.3 以降から追加された機能です。
どちらも SET TRANSACTION コマンドを使用します。それぞれについて解説します。
2.3.1.読み取り専用トランザクションを使う
読み取り専用トランザクションを使うには、SET TRANSACTION READ ONLY を使います。
構文は次のとおりです。
構文
SET TRANSACTION {READ ONLY │ READ WRITE}
READ WRITE
現行トランザクションを読み書き両用にします。デフォルトのトランザクションモードです。
READ ONLY
現行トランザクションを読み取り専用にして、トランザクションレベルの読み取り一貫性を施
行します。読み取り専用にすると、SET TRANSACTION 発行時点のデータと、そのセッション
自身で変更したデータを読みとることが保証されます。ほかのユーザーがデータを変更しても、
現行セッションは影響を受けません。
解説
SET TRANSACTION 文は現行トランザクションだけに影響します。ほかのユーザーやほかのトラ
ンザクションには影響しません。そして COMMIT もしくは ROLLBACK を発行すると読み取り
専用トランザクションは終了します。
読み取り専用トランザクションでは、トランザクションレベルの読み取り一貫性を施行するた
めに、いかなる追加のロックも獲得しません。したがってほかのトランザクションは、読み取り
専用トランザクションが問い合わせているデータに対し、同時に更新できます。
その代わり長時間にわたる問い合わせでは、「ORA-01555:スナップショットが古すぎます」
というエラーが発生する可能性があります。そのときにはロールバックセグメントのサイズを大
きくしたり、数を増やすことによって対処してください。
SET TRANSACTION 文はトランザクションの先頭に記述します。未コミットのトランザクシ
ョンがあるときに SET TRANSACTION 文を発行すると、次のエラーが発生します。
ORA-01453: SET TRANSACTION はトランザクションの最初の文でなければなりません。
SET TRANSACTION 文を実行する前に必ず COMMIT を入れるようにすれば、このエラーは
回避できます。
7 排他制御の理解とアプリケーションへの適用
読み取り専用トランザクションは、ローカル問い合わせと分散問い合わせによってアクセスさ
れるすべてのノードが対象です。
読み取り専用トランザクション内では、INSERT や UPDATE、DELETE、SELECT FOR
UPDATE は使用できません。そのような DML を発行すると、次のエラーが発生します。
ORA-01456: READ ONLY トランザクションでは挿入/削除/更新ができません。
実際の例を説明します。
Aさん
Bさん
1 SET TRANSACTION READ ONLY;
トランザクションが設定されました。
2 SELECT empno,ename,job FROM emp
WHERE empno=8000;
EMPNO ENAME
JOB
--------- ---------- --------8000 TESTUSER
SALESMAN
3
UPDATE emp SET JOB='CLERK'
WHERE empno=8000;
1 行が更新されました。
COMMIT;
4 SELECT empno,ename,job FROM emp
WHERE empno=8000;
EMPNO ENAME
JOB
--------- ---------- --------8000 TESTUSER
SALESMAN
5 COMMIT;
6 SELECT empno,ename,job FROM emp
WHERE empno=8000;
EMPNO ENAME
JOB
--------- ---------- --------8000 TESTUSER
CLERK
1.読み取り専用トランザクションを開始します。
3.B さんは社員番号 8000 番の社員の職種を CLERK に変更します。
4.読み取り専用トランザクション中なのでAさんは B さんの影響を受けません。
5.COMMIT で読み取り専用トランザクションを終了します。
6.読み取り専用トランザクションを終了したので、Bさんの変更が見えるようになりました。
排他制御の理解とアプリケーションへの適用 8
2.3.2.シリアライザブルトランザクションを使う
シリアライザブルトランザクションを利用するには、SET TRANSACTION ISOLATION
LEVEL 文を使ってトランザクションのアイソレーションレベルを定義します。構文は次のとお
りです。
ヒント
「シリアライザブル」と「アイソレーションレベル」は、それぞれ「直列可能」や「分離レベ
ル」とマニュアルでは表記されています。
構文
SET TRANSACTION ISOLATION LEVEL {SERIALIZABLE │ READ COMMITED}
SERIALIZABLE
セッション内のトランザクションは、SQL92 に指定されているアイソレーションレベルの
SERIALIZABLE を使用します。読み取り専用トランザクションと最も異なる点は、更新処理が
できることです。
ただし更新対象のデータがほかのトランザクションに変更されているときには、その更新処理
は失敗します。アイソレーションレベル SERIALIZABLE を利用するには、COMPATIBLE 初期
化パラメータを 7.3.0.0.0 以上に設定します。
READ COMMITED
デフォルトのトランザクションモードです。問い合わせ対象となるデータは、その問い合わせ
が発行される直前までにコミットされたデータだけです。
解説
アイソレーションレベルの有効範囲や、SET TRANSACTION 文をトランザクションの先頭に記述
しなければいけないといった制限は、読み取り専用トランザクションと同じです。一番の違いは、
行データを変更できることです。
ただし変更できる行データは、シリアライザブルトランザクション開始以前にコミットされて
いる行データに限られます。シリアライザブルトランザクション開始以降に、ほかのユーザーが
コミットした行データを変更しようとすると、次のエラーが発生します。
ORA-08177: このトランザクションのアクセスを逐次化できません。
このエラーが発生したときには、エラーの原因となった文だけがロールバックされます。それ
までのトランザクションすべてがロールバックされるわけではありません。したがってこの状態
では、次のアクションが可能です。
l
引き続き、ほかの DML を実行する。
l
この直前までのトランザクションをコミットする。
l
トランザクション全体をロールバックする。
Oracle は、各データブロックにトランザクションの制御情報を格納します。したがってアイソ
レーションレベルをシリアライザブルに設定するときには、表のエクステントパラメータとして
INITRANS を少なくとも 3 以上に設定してください。
ヒント
「ALTER SESSION SET ISOLATION_LEVEL=SERIALIZABLE」を使えば、セッション内における
デフォルトのアイソレーションレベルを変更できます。
9 排他制御の理解とアプリケーションへの適用
実際の例を説明します。
Aさん
Bさん
1 SET TRANSACTION ISOLATION
LEVEL SERIALIZABLE;
トランザクションが設定されました。
2
DELETE FROM emp WHERE empno=8000;
1 行が削除されました。
COMMIT;
3 SELECT empno,ename,job FROM emp
WHERE empno=8000;
EMPNO ENAME
JOB
--------- ---------- --------8000 TESTUSER
SALESMAN
4 INSERT INTO emp
VALUES(8001,'TESTUSER1','SALESMAN',7698,
'98-04-01',700,100,30);
1 行が作成されました。
5 DELETE FROM emp WHERE empno=8000;
エラー行: 1: エラーが発生しました。
ORA-08177: このトランザクションのアクセ
スを逐次化できません。
6 COMMIT;
7 SELECT empno,ename,job FROM emp
WHERE empno=8001;
EMPNO ENAME
JOB
--------- ---------- --------8001 TESTUSER1
SALESMAN
1.アイソレーションレベルを SERIALIZABLE に設定します。
2.社員番号 8000 番の社員データを削除します。
3.アイソレーションレベルを SERIALIZABLE にしているので、Bさんの影響は受けません。
4.新しく社員番号 8001 番の社員データをインサートします。シリアライザブルトランザクション
ではデータの変更が可能です。
5.社員番号 8000 番の社員データを削除します。しかし、この行データはシリアライザブルトラン
ザクションを開始したあとにAさんが削除しているので、ORA-08177 が発生します。このと
き、この DELETE 文だけがロールバックされます。
6.ここまでのトランザクションをコミットします。
7.ORA-08177 では文レベルのロールバックしか発生しないので、4で行ったインサートは有効で
す。
排他制御の理解とアプリケーションへの適用 10
3.Oracle におけるロック管理の理解
Oracle には暗黙的なロックと明示的なロックがあります。暗黙的なロックとは、Oracle が自動
的に施行するロックのことです。明示的なロックとは、ユーザーが明示的に獲得するロックのこ
とです。それぞれについて解説します。
3.1.暗黙的にロックする
Oracle では、INSERT/UPDATE/DELETE の対象になっているデータに対して、トランザクシ
ョンが終了するまで排他ロックがかかります。そのため複数のユーザーが同時に同じデータを変
更する DML を発行すると、待ち状態になります。INSERT/UPDATE/DELETE それぞれの場合
について、動きを確認します。
Aさん
挿入のとき
Bさん
1 INSERT INTO emp(empno,ename,job)
VALUES(9000,'TEST','SALESMAN');
1 行が作成されました。
2
INSERT INTO emp(empno,ename,job)
VALUES(9000,'TEST','SALESMAN');
3 COMMIT;
エラー行: 1: エラーが発生しました。
4
ORA-00001:一意制約:(SCOTT.PK_EMP) に反し
ています。
1.社員番号 9000 番のデータを挿入します。
2.社員番号に主キー制約があるので、重複する社員番号を持つデータをインサートすると待ち状
態になります。
4.Aさんがコミットして、はじめてBさんの SQL が実行されます。しかし既に 9000 番のデータ
は存在するので、一意制約違反になります。もちろん主キー制約がないときにはエラーになら
ず、待ち状態にもなりません。
削除のとき
Aさん
Bさん
1 DELETE FROM emp WHERE empno = 9001;
1 行が削除されました。
2
DELETE FROM emp WHERE empno = 9001;
3 COMMIT;
0行が削除されました。
4
1.社員番号 9000 番のデータを削除します。
2.社員番号 9000 番のデータにロックがかかっているので待ち状態になります。
4.Aさんがコミットして、はじめてBさんの SQL が実行されます。エラーにはなりませんが、A
さんは既に削除しているので削除行数は0になります。
11 排他制御の理解とアプリケーションへの適用
更新のとき
Aさん
Bさん
1 SELECT empno,ename,sal FROM emp
SELECT empno,ename,sal FROM emp
WHERE empno=7900;
EMPNO ENAME
WHERE empno=7900;
SAL
EMPNO ENAME
--------- ---------- --------7900 JAMES
SAL
--------- ---------- ---------
950
7900 JAMES
950
2 UPDATE emp SET sal = sal + 100
WHERE empno = 7900;
1 行が更新されました。
3
UPDATE emp SET sal = sal + 100
WHERE empno = 7900;
4 COMMIT;
5
1 行が更新されました。
6
SELECT empno, ename, sal FROM emp
WHERE empno = 7900
EMPNO ENAME
SAL
--------- ---------- --------7900 JAMES
1150
1.AさんとBさん、どちらから見ても同じデータです。
2.社員番号 7900 番の給与に 100 を足します。
3.社員番号 7900 番のデータにロックがかかっているので、待ち状態になります。
5.Aさんがコミットしてロックが解除されたので、Bさんの SQL が実行されます。更新のときは
エラーになりません。
6.Bさんは 950 に 100 を足したつもりでしたが、先にAさんが 100 を足していたため、1150 にな
りました。この問題については4章で解説します。
まとめ
これらの結果からわかるように、普通に SQL を実行するだけでは思うような結果が得られない
ことがあります。したがってアプリケーションを作成するときには、次に紹介する明示的なロッ
クと組み合わせることが一般的です。
3.2.明示的にロックする
Oracle はデータの整合性を保つために、デフォルトで必要なロックを獲得します。しかし明示
的にロックを獲得することによって、デフォルトロックを無効にできます。
明示的にロックを獲得するには、次の2つの方法があります。
l
LOCK TABLE 文で表ロックを獲得する。
l
SELECT 文に FOR UPDATE 句をつけて行ロックを獲得する。
排他制御の理解とアプリケーションへの適用 12
3.2.1.明示的に表ロックを獲得する
表全体をロックするときは LOCK TABLE 文を使用します。表全体のデータを更新するような
バッチ処理では、あらかじめ表ロックを獲得することによって、トランザクション途中のロック
待ちを防止できます。構文は次のとおりです。
構文
LOCK TABLE {tablename│viewname} [,tablename...] IN mode [NOWAIT]
tablename|viewname
ロックする表を指定します。ビューを指定すると、ビューのもとになる表がロックされます。
mode
ロックするモードを指定します。Oracle には5つのロックモードがあり、その中でも次の2つ
は重要です。詳細は参考文献1の「22-1 データの同時実行性と一貫性」をご覧ください。
EXCLUSIVE MODE 排他表ロック。排他ロックを獲得したトランザクションだけが、その表
に対し排他的に書き込めます。ほかのトランザクションは読み取りだけが可能です
SHARE MODE 共有表ロック。ほかのトランザクションは問い合わせ、LOCK TABLE IN
SHARE MODE、SELECT FOR UPDATE だけが可能です。
NOWAIT
指定した表がロックされているときには、すぐに制御を戻し、「ORA-00054:リソースビジー、
NOWAIT が指定されていました」を返します。
解説
指定したモードで 1 つまたは複数の表をロックします。表もしくはビューは、自スキーマ内に無け
ればなりません。無い場合は LOCK ANY TABLE システム権限が付与されているか、表またはビ
ューに対するオブジェクト権限が必要です。
複数の表を指定したとき、表のロック順序は不定です。決まった順序でロックしたいときには、
1つの LOCK TABLE 文に複数の表を指定するのではなく、それぞれ別の SQL 文にしてくださ
い。
例 1)emp 表を排他モードでロックする。ほかのトランザクションにロックされているときには、
そのロックが解除されるまで待機する。ほかのトランザクションは、emp 表に対して問い合わ
せしかできない。
LOCK TABLE emp IN EXCLUSIVE MODE
例 2)emp 表を共有モードでロックする。ほかのトランザクションに排他ロックされているときに
は、そのロックが解除されるまで待機する。ほかのトランザクションは emp 表に対して、問い
合わせ、LOCK TABLE IN SHARE MODE、SELECT FOR UPDATE だけが可能。
LOCK TABLE emp IN SHARE MODE
例 3)emp 表を排他モードでロックする。ほかのトランザクションにロックされているときには、
ロックをあきらめ、すぐに制御を戻す。
LOCK TABLE emp IN EXCLUSIVE MODE NOWAIT
例 4)emp 表と dept 表を排他モードでロックする。ロックできない表が1つでもあるときには、ロ
ックをあきらめ、すぐに制御を戻す。
LOCK TABLE emp, dept IN EXCLUSIVE MODE NOWAIT
排他表ロックと共有表ロック
13 排他制御の理解とアプリケーションへの適用
ここで排他表ロックと共有表ロックの違いを整理します。
排他表ロック
特定の表に対し排他的な操作をしたいときに使用します。
排他ロックされた表に対して、ほかのトランザクションは問い合わせだけが可能です。DML や
ロックを獲得する SQL は実行できません。
共有表ロック
トランザクションレベルの読み取り一貫性を施行したいときなどに使用します。共有表ロック
ではほかのトランザクションによる更新をブロックするため、「ORA-01555:スナップショット
が古すぎます」は発生しません。
共有ロックされた表に対して、ほかのトランザクションは問い合わせ、LOCK TABLE IN
SHARE MODE、SELECT FOR UPDATE だけが可能です。したがって同じ表に対し複数のトラ
ンザクションが LOCK TABLE IN SHARE MODE を実行できます。そのときにはすべてのトラ
ンザクションで更新処理ができません。
注意することは他のトランザクションに共有ロックされている表に対して SELECT FOR
UPDATE は可能だということです。通常、更新処理を行うときは SELECT FOR UPDATE でロ
ックしてから UPDATE や DELETE を実行します。ところが共有表ロックがかかっていると、
SELECT FOR UPDATE でロックを獲得しているにもかかわらず、UPDATE や DELETE は待ち
状態になります。
つまり同一の表に対し共有表ロックと OLTP が混在する環境では、長時間の待ちやデッドロッ
クが発生する可能性があります。したがって、そのときには共有表ロックではなく排他表ロック
を使用します。
3.2.2.明示的に行ロックを獲得する
明示的に行ロックを獲得するときには SELECT 文に FOR UPDATE 句を指定します。すると問
い合わせ対象行には排他行ロックがかかります。構文は次のとおりです。
構文
SELECT col1, col2, ... FROM tablename FOR UPDATE [OF [tablename.]column] [NOWAIT]
FOR UPDATE OF
問い合わせ対象のテーブルが複数あるときに OF 句を省略すると、FROM 句に指定したすべて
のテーブルの問い合わせ対象行がロックされます。OF 句に列名を指定することによって、ロッ
ク対象のテーブルを特定できます。
NOWAIT
問い合わせ対象行がロックされているときには、すぐに制御を戻し、「ORA-00054:リソース
ビジー、NOWAIT が指定されていました」を返します。
解説
SELECT FOR UPDATE の問い合わせ対象になった行はロックされます。これらのロックされた行
を、ほかのユーザーは更新できません。ロックは、そのトランザクションが終了するまで続きま
す。
ほかのユーザーが先にロックしていたときには、そのロックが解除されるまで待機します。す
ぐに制御を戻したいときには NOWAIT オプションを指定します。デフォルトでは待機状態がつ
づく可能性があるため、通常は NOWAIT オプションを使用します。
排他制御の理解とアプリケーションへの適用 14
ヒント
タイムアウト時間を設定して、一定の時間だけ待つことはできません。そのような仕組みにしたい
ときは、プログラム側で行います。
FOR UPDATE 句は、次の要素と一緒には使えません。
l
DISTINCT 演算子
l
GROUP BY 句
l
集合演算子
l
グループ関数
l
CURSOR 演算子
l
副問い合わせの中
例 1)社員番号 7900 番の行をロックする。ほかのユーザーにロックされているときには、そのロッ
クが解除されるまで待機する。
SELECT * FROM emp WHERE empno=7900 FOR UPDATE
例 2)社員番号 7900 番の行をロックする。ほかのユーザーにロックされているときには、ロックを
あきらめ、すぐに制御を戻す。
SELECT * FROM emp WHERE empno=7900 FOR UPDATE NOWAIT
例 3)社員番号 7900 番の行(emp 表)と、この社員が属する部門の行(dept 表)をロックする。
SELECT d.dname, e.ename FROM emp e, dept d
WHERE d.deptno = e.deptno and e.empno = 7900 FOR UPDATE NOWAIT
例 4)社員番号 7900 番の行(emp 表)だけをロックする。
SELECT d.dname, e.ename FROM emp e, dept d
WHERE d.deptno = e.deptno and e.empno = 7900 FOR UPDATE OF e.ename NOWAIT
この例ではロックする表を特定するために OF 句でカラム名を指定しています。このときに表
明にエイリアスをつけているときには、OF 句でも必ずエイリアスを付けてください。エイリア
スをつけているにもかかわらず「表名.カラム名」のような書き方をすると、「ORA-00904:列
名が無効です」が発生します。次の例はエラーになる場合と、ならない場合です。
× emp.ename
○ e.ename
○ ename
15 排他制御の理解とアプリケーションへの適用
リモートデータベースへのFOR UPDATE について
データベースリンク先のリモートデータベースの表に対して FOR UPDATE を使ったロックは
可能です。ただし FOR UPDATE 句でロックする表は、すべて同じデータベース内になければな
りません。次の1と4は、ロック対象の表が異なるデータベースにあるのでエラーになります。
1.SELECT d.dname, e.ename FROM [email protected] e, dept d エラー
WHERE d.deptno = e.deptno FOR UPDATE
2.SELECT d.dname, e.ename FROM [email protected] e, dept d ローカル表だけをロック
WHERE d.deptno = e.deptno FOR UPDATE OF d.dname
3.SELECT d.dname, e.ename FROM [email protected] e, dept d リモート表だけをロック
WHERE d.deptno = e.deptno FOR UPDATE OF e.ename
4.SELECT d.dname, e.ename FROM [email protected] e, dept d エラー
WHERE d.deptno = e.deptno FOR UPDATE OF d.dname, e.ename
3.2.3.明示的行ロックの動作
NOWAIT あり/なしに分けて、実際の SELECT FOR UPDATE の動作を解説します。
SELECT ... FOR UPDATE(NOWAIT オプションなし)
Aさん
Bさん
1 SELECT * FROM dept WHERE deptno = 10 FOR
UPDATE;
DEPTNO DNAME
LOC
------ ------------ ---------10 ACCOUNTING
NEW YORK
2
SELECT * FROM dept WHERE deptno = 10;
DEPTNO DNAME
LOC
------ ------------ ---------10 ACCOUNTING
3
NEW YORK
SELECT * FROM dept WHERE deptno = 10 FOR
UPDATE;
4 COMMIT;
5
DEPTNO DNAME
LOC
------ ------------ ---------10 ACCOUNTING
NEW YORK
1.部門番号 10 の行にロックをかけます。
2.ロックされていても読み取りだけならば可能です。
3.部門番号 10 の行にロックをかけます。ロック中のため待ち状態になります。
4.COMMIT でロックを解放します。
5.Aさんがロックを解放したので、ロックがかかります。
排他制御の理解とアプリケーションへの適用 16
SELECT ... FOR UPDATE(NOWAIT オプションあり)
Aさん
Bさん
1 SELECT * FROM dept WHERE deptno = 10
FOR UPDATE NOWAIT;
DEPTNO DNAME
LOC
------ ------------ ---------10 ACCOUNTING
NEW YORK
2
SELECT * FROM dept WHERE deptno = 10
FOR UPDATE NOWAIT;
行: 1:にエラーが発生しました。ORA-00054:
リソースビジー、NOWAIT が指定されていま
した
1.部門番号 10 の行にロックをかけます。
2.部門番号 10 の行に NOWAIT オプションつきでロックかけます。Aさんがロックしているので
ORA-00054 が返ってきます。
まとめ
NOWAIT オプションでは、問い合わせ対象行がロックされていると、すぐにエラーが返ってきま
す。それに対し NOWAIT オプションなしでは、ロックが解除されるまで永遠に待ち状態になり
ます。これが FOR UPDATE と FOR UPDATE NOWAIT の違いです。
3.3.参照整合性制約を定義したときの暗黙的ロック
参照整合性制約を定義すると、その関連づけを維持するために、Oracle は適切なロックを獲得
します。スキーマの物理構造(インデックスの有無)によっては、ロックの範囲が変わるので注意
が必要です。このセクションではそれを実際に確認します。
図 4 EMP 表と DEPT 表
SCOTT ユーザーの DEPT 表と EMP 表は、DEPTNO をキーとして参照整合性制約が定義され
ています。DEPT 表と EMP 表は 1 対 N の関係があり、(主キー側の)DEPT 表を親表、(外部キー
側の)EMP 表を子表と呼びます。
テストのために、この参照整合性制約を「DELETE CASCADE」に変更します。これで親表の
すべての行を DELETE できるようになります。
SQL> ALTER TABLE emp DROP CONSTRAINT fk_deptno;
表が変更されました。
SQL> ALTER TABLE emp ADD CONSTRAINT fk_deptno
1 FOREIGN KEY (deptno) REFERENCES dept(deptno)
2 ON DELETE CASCADE;
表が変更されました。
17 排他制御の理解とアプリケーションへの適用
外部キーにインデックスがないとき
まず外部キーにインデックスが定義されていない場合について解説します。
外部キーにインデックスが定義されていないとき、親表に DELETE 文を実行すると、子表に共
有行排他表ロックがかかります。また子表が参照する列を変更する UPDATE 文を実行すると、
子表に共有表ロックがかかります。INSERT 文を実行しても、子表にロックはかかりません。し
たがって DELETE や UPDATE を含むトランザクションが終了するまで、子表に対して読み取り
しかできません。
子表に対して INSERT/UPDATE/DELETE を実行しても、親表にロックは獲得しません。
DELETE のとき
次の SQL を実行します。
SQL> DELETE FROM dept WHERE deptno=10;
1 行が削除されました。
このとき削除されるのは、DEPT 表の DEPTNO=10 を満たす行と、EMP 表の DEPTNO=10 を
満たす行です。したがってこれらの行は排他ロックされます。しかしこれ以外にも、子表(EMP)
全体に対し共有行排他表ロックが獲得されます。
このときのロックの状況を SYSTEM ユーザーで確認します。別に SQL*Plus を起動し、次の
SQL を実行します。
SQL> CONNECT SYSTEM/MANAGER
SQL> SELECT sid FROM v$session WHERE username='SCOTT';
SID
--------14
SQL> SELECT v.lmode, d.object_name
2 FROM dba_objects d, v$lock v
3 WHERE v.sid=14 AND d.object_type='TABLE' AND v.id1 = d.object_id;
LMODE OBJECT_NAME
--------- -----------3 DEPT
5 EMP
結果を見ると、DEPT 表には行排他表ロックがかけられていますが、EMP 表には共有行排他表
ロックがかけられています。このためトランザクションが終了するまでは、EMP 表に挿入、更新、
削除はできません。
LMODE 値の意味は以下のとおりです。
LMODE の値
意味
2
行共有 (RS,SS)
3
行排他 (RX,SX)
4
共有 (S)
5
共有行排他 (SRX,SSX)
6
排他 (X)
表 1 ロックモードの意味
排他制御の理解とアプリケーションへの適用 18
UPDATE のとき
親表の子表が参照している列を更新するときも、子表に共有表ロックがかかります。子表が参
照していない列(DEPTNO 以外)を更新するときは、子表にロックはかかりません。SCOTT ユー
ザーで次の SQL を実行します。
注意
連続して実行するときは、以前のロックを解除するために ROLLBACK を実行してください。
SQL> UPDATE dept SET deptno=99 WHERE deptno=40;
1 行が更新されました。
ヒント
参照整合性制約が定義されているので、関連づけられた子レコードがある行(deptno=40 以外)は
UPDATE できません。
SYSTEM ユーザーでロックの状況を確認します。
SQL> SELECT v.lmode, d.object_name
2 FROM dba_objects d, v$lock v
3 WHERE v.sid=14 AND d.object_type='TABLE' AND v.id1 = d.object_id;
LMODE OBJECT_NAME
--------- -----------3 DEPT
4 EMP
EMP 表に対して共有表ロックがかかっています。DELETE のときとロックレベルは異なりま
すが、このときもトランザクションが終了するまで EMP 表に対して挿入、更新、削除はできま
せん。業務アプリケーションで、子表から参照している列(主キー)を変更することは少ないです
が、該当するときには注意する必要があります。
外部キーにインデックスがあるとき
こんどは外部キーにインデックスが定義されている場合です。
外部キーにインデックスが定義されているときは、子表に挿入、更新、削除を実行しても、親
表に対していかなるロックも獲得しません。また親表に挿入、更新、削除を実行しても、子表に
対する挿入、更新、削除を妨げることはありません。ただし DELETE CASCADE 指定している
ときに親表を削除すると、親表と子表の行を順に削除したときと同じロックがかかります。
テストのために EMP 表の DEPTNO 列にインデックスを作成します。
SQL> CREATE INDEX fk_emp_deptno ON emp(deptno);
索引が作成されました。
先ほどと同じ SQL を実行して、外部キーにインデックスが無いときとの違いを確認します。
DELETE のとき
SCOTT ユーザーで次の SQL を実行します。
SQL> DELETE FROM dept WHERE deptno=10;
1 行が削除されました。
19 排他制御の理解とアプリケーションへの適用
ロックの状況を SYSTEM ユーザーで確認します。
SQL> SELECT v.lmode, d.object_name
2 FROM dba_objects d, v$lock v
3 WHERE v.sid=14 AND d.object_type='TABLE' AND v.id1 = d.object_id;
LMODE
--------3
3
OBJECT_NAME
-----------EMP
DEPT
EMP 表には行排他ロックだけなので、こんどは挿入、削除、更新、いずれも可能です。
UPDATE のとき
SCOTT ユーザーで次の SQL を実行します。
SQL> UPDATE dept SET deptno=99 WHERE deptno=40;
1 行が更新されました。
ロックの状況を SYSTEM ユーザーで確認します。
SQL> SELECT v.lmode, d.object_name
2 FROM dba_objects d, v$lock v
3 WHERE v.sid=14 AND d.object_type='TABLE' AND v.id1 = d.object_id;
LMODE OBJECT_NAME
--------- -----------3 DEPT
子表(EMP)には一切ロックがかかっていません。
まとめ
参照整合性制約が定義してある親表と子表に対して、同時に更新や削除を行うときには、外部キー
にインデックスを作成することによって同時実行性が最大になります。またこのインデックスは
問い合わせ時に使用することが多いのでパフォーマンスの面でも有効です。したがって外部キー
にはインデックスを作成することをお勧めします。
3.4.デッドロックとは
デッドロックとは、相手のトランザクションがロックしているリソースをお互いに獲得しよう
とした結果、永遠に待ち状態になる現象です。
デッドロック発生
A さん
A
B
C
D
B さん
図 5 デッドロック
排他制御の理解とアプリケーションへの適用 20
次のような順序で処理を行うトランザクションがあるとします。
n
Aさん A→B→C→D
n
Bさん D→C→B→A
このトランザクションを同時に実行すると、デッドロックが発生する可能性があります。
まずAさんは、A と B の処理を終らせて C へ取りかかろうとします。そのとき C はBさんにロ
ックされているので、ロックが解除されるのを待ちます。逆にBさんは C の処理を終らせてBへ
取りかかろうとしたときに、Aさんにロックされているので、ロックが解除されるのを待ちます。
これでは解除されることのないロックをお互いに待ち合うことになり、止まったままになりま
す。
3.4.1.デッドロックの検出
Oracle にはデッドロックの自動検出機能があるので、このような問題は起こりません。デッド
ロックが発生したときには、デッドロックの原因となっている SQL 文を自動的にロールバックし
ます。実際のデッドロックの例を紹介します。
Aさん
Bさん
1 UPDATE emp SET comm=100
WHERE empno=7369;
1 行が更新されました。
2
UPDATE emp SET comm=200
WHERE empno=7934;
1 行が更新されました。
3 UPDATE emp SET deptno=20
WHERE empno=7934;
4
UPDATE emp SET deptno=20
WHERE empno=7369;
5 ORA-00060: リソース待機の間にデッドロッ
クが検出されました。
6 ROLLBACK;
7
1 行が更新されました。
8
COMMIT;
コミットが完了しました。
1.Aさんは社員番号 7369 のデータを更新します。
2.Bさんは社員番号 7934 のデータを更新します。
3.Aさんは社員番号 7934 のデータを更新しますが、Bさんがロックしているので待ち状態になり
ます。
4.Bさんは社員番号 7369 のデータを更新しますが、Aさんがロックしているので待ち状態になり
ます。これでデッドロック状態になります。
5.Oracle はデッドロックを自動的に検出し、デッドロックの原因となる SQL 文、この場合は3の
SQL 文だけをロールバックします。1の SQL 文は有効です。
6.デッドロックが検出されたので、トランザクション全体を明示的にロールバックします。
7.社員番号 7369 のデータに対するロックが解除されたので、Bさんが4で実行した SQL 文が処
理されました。
21 排他制御の理解とアプリケーションへの適用
ここで重要なことは、ロールバックはトランザクションレベルではなく、デッドロックの原因
となっている単一の SQL 文だけだということです。したがってエラーを通知されたトランザクシ
ョンでは、明示的に ROLLBACK を実行する必要があります。もちろん COMMIT することも可
能です。しかしトランザクションは論理的な1つの更新単位なので、データの整合性が失われる
ことになります。
重要
自動的にロールバックされるのは、デッドロックの原因となる単一の SQL 文だけです。
リモートデータベースに対するデッドロックはタイムアウトで判定します。ロック待ちがタイ
ムアウトの時間に達すると、その SQL 文はロールバックされ次のエラーが発生します。
ORA-02049: Timeout: 分散トランザクションがロックを待機しています
タイムアウト時間の設定は INIT.ORA パラメータで行います。30 秒に設定したいときは、パラ
メータファイルに次の一行を追加します。
DISTRIBUTED_LOCK_TIMEOUT=30
3.4.2.デッドロックの回避
単一の表に対するデッドロックは、あらかじめ SELECT FOR UPDATE NOWAIT でロックす
ることで回避できます。また複数表のデッドロックは、同じ順序で表をロックすれば回避できま
す。たとえばマスター・ディテールの関係を持つ表の場合、必ずマスター側から更新するとか、
そのような関係のない表では、表の名前順に更新するといったルールです。適切に設計したルー
ルに基づきアプリケーションを作成すれば、デッドロックの可能性を最小限に抑えられます。
3.5.ロックを強制的に解除する
ロックをしていたプログラムがハングアップしてしまい、表がロックされたままになる。
これはよくあるトラブルの1つです。特にアプリケーション開発中はプログラムが不安定なた
め、決して珍しいことではありません。またアプリケーションプログラム自体は正常でも、オペ
レーティングシステムがハングアップした結果、アプリケーションまで一緒にハングアップする
こともあります。
Oracle に限らず多くのデータベースでは、ロックを獲得したプログラムがハングアップすると
ロックはそのままになります。これではロックされている表を変更できないため、結局データベ
ースの再起動という手段がとられます。
しかし Oracle には、再起動しなくても、そのロックを手動もしくは自動で解除する方法があり
ます。1番目が手動で、それ以外は自動で行う方法です。
l
Oracle Enterprise Manager の Instance Manager もしくは ALTER SYSTEM KILL
SESSION コマンドでセッションを強制終了する。
l
Net8/SQL*Net の expire_time を使う。
l
ユーザープロファイルを使う。
本書では簡単に説明するので、詳細は参考文献 5、参考文献 6 をご覧ください。
排他制御の理解とアプリケーションへの適用 22
3.5.1.セッションを強制終了する
ALTER SYSTEM KILL SESSION コマンドを使うと、特定のユーザーセッションを任意に切
断できます。たとえば SID が 10、SERIAL 番号が 344 のセッションを終了させるときは、次の
SQL を実行します。
ALTER SYSTEM KILL SESSION '10,344'
セッションを終了させると、トランザクションはロールバックされ、獲得していたロックなど
のリソースはすべて解放されます。なお ALTER SYSTEM 文を実行するためには、ALTER
SYSTEM システム権限が必要です。
セッションを識別するための SID や SERIAL 番号は、V$SESSION 動的パフォーマンス表から
取得します。次の例を見てください。現在データベースに接続しているユーザー情報を取得する
ために、SYSTEM ユーザーは V$SESSION 表に対し問い合わせを実行しています。
SQL>
SQL>
2
3
CONNECT SYSTEM/MANAGER
SELECT sid,serial#,username,osuser,program,status
FROM v$session
WHERE username IS NOT NULL;
SID SERIAL# USERNAME OSUSER
--- ------- --------- --------7
23265 SCOTT
USER1
12
610 SYSTEM
USER2
10
344 SCOTT
USER4
PROGRAM
--------------PLUS80W.EXE
PLUS80W.EXE
PLUS80W.EXE
STATUS
-----------------INACTIVE
ACTIVE
INACTIVE
USERNAME はオラクルのユーザー名で、OSUSER はオペレーティングシステムのユーザー
名です。PROGRAM は、Oracle に接続しているプログラムの実行ファイル名です。STATUS は
そのセッションの状態を表しています。強制終了したセッションの STATUS は「KILLED」にな
ります。
Instance Manager を使う
Oracle Enterprise Manager の Instance Manager を使用する方法を説明します。
1.Instance Manager を起動し、データベース管理者ユーザーで接続します。
ヒント
Oracle Enterprise Manager のリポジトリが無くても、Instance Manager は使用できます。
2.ウィンドウの左側にあるツリーの中から[セッション]をクリックします。するとデータベースの
セッション情報が表示されます。
23 排他制御の理解とアプリケーションへの適用
3.ウィンドウの右側のペインから終了したいセッションを選択し、マウスの右ボタンをクリック
します。ポップアップメニューが表示されるので、[切断]−[即時]を選択します。これでセッ
ションは切断されました。
3.5.2.EXPIRE_TIME を使う
EXPIRE_TIME とは、TCP/IP の keepalive を利用して無効になった接続を自動的に切断する機
能です。SQL*Net V2.1(Oracle7 R7.1)からの機能ですが、サポートされるバージョンはプラット
フォームによって異なります。Windows NT 版の Oracle7 では、V2.1 以降でもサポートされてい
ないバージョンがあります。詳細は保守契約先のテクニカルサポートセンターへお問い合わせく
ださい。
この機能を利用するときには、サーバー側の sqlnet.ora ファイルに次の1行を追加します。
SQLNET.EXPIRE_TIME=n
n は、無効になってから切断するまでの時間[分]です(推奨は 10)。Windows NT ではOSの仕様
上、n の最大値は 16 に制限されています。また keepalive を利用しているため、TCP/IP の設定に
よっては正常に作動しないこともあります。
3.5.3.プロファイルを使う
プロファイルとは、ユーザーに対してシステムリソースを制限するオブジェクトです。プロフ
ァイルを利用することによって、アイドル時間の長いセッションを自動的に切断できます。無効
になった接続だけでなく、データベースと何もやりとりを行っていないセッションも切断の対象
になることに注意してください。
次の例では、SYSTEM ユーザーが foo というプロファイルを作成し、SCOTT ユーザーに割り
当てています。このプロファイルは、次に接続したときから有効になります。
SQL> CONNECT SYSTEM/MANAGER
SQL> CREATE PROFILE foo LIMIT
2 IDLE_TIME 30;
プロファイルが作成されました。
SQL> ALTER USER SCOTT PROFILE foo;
ユーザーが変更されました。
排他制御の理解とアプリケーションへの適用 24
4.マルチユーザーアプリケーションのロック戦略
これまでの説明で Oracle には自動的な排他制御機能があることがわかりました。しかし実際の
アプリケーションを構築するうえで、これだけでは不十分です。次の図を見てください。これは
一般的なアプリケーションにおけるデータ更新の概念図です。
②変更
①読み込み
③書き込み
図 6 データ変更の概念図
①データベースからデータを読み取る。
②読み取ったデータを変更する。
③変更したデータをデータベースに書き込む。
①で読み取ったデータは、クライアントマシンのメモリ上もしくはディスク上に格納されます。
②の段階では、メモリ上にあるデータを書き換えます。そして③で、はじめてデータベースに書
き込みます。つまり③でデータベースに反映する前に、ほかのマシンからこのデータを変更した
ときには、データの不整合が発生することになります。このようなデータの不整合を防止するた
めには、アプリケーション側でも排他制御を施行する必要があります。
アプリケーション側で排他制御を考えないときの例を解説します。口座番号10の預金口座に
は¥1,000 の残高があります。これをAさん、Bさんがそれぞれ変更します。
残高
1 ¥1,000
Aさん
Bさん
口座番号 10 の残高¥1,000 を読みと
る。
口座番号 10 の残高¥1,000 を読みと
2
る。
3 ¥6,000
残高に¥5,000 を追加して、データベー
スに書き込む。
4 ¥2,000
残高に¥1,000 を追加して、データベー
スに書き込む。
本来、AさんとBさんの追加を合計して¥7,000 の残高になるべきです。しかしAさんの追加は
Bさんに上書きされているので、¥2,000 の残高になりました。この結果から、アプリケーション
側でも排他制御が必要なことがわかります。
アプリケーション側の排他制御の方法には、大別すると2つのロック戦略があります。楽観的
ロック(Optimistic Lock)と悲観的ロック(Pessimistic Lock)です。この2つは、どちらが優れてい
るというものではありません。アプリケーションの要件、目的に応じて最適なものを選択しま
す。
25 排他制御の理解とアプリケーションへの適用
4.1.悲観的ロック(Pessimistic Lock)とは
悲観的ロックとは、「自分が操作しているデータは、ほかのユーザーに変更される可能性が高
い」と考えたロック戦略です。こんどは悲観的ロックの例です。
残高
1 ¥1,000
Aさん
Bさん
口座番号 10 の残高¥1,000 をロックつ
きで読みとる。
口座番号 10 をロックつきで読みとる。
2 ¥1,000
Aさんがロック中なので待ち状態。
3 ¥1,000
残高に¥5,000 を追加する。この段階で
はアプリケーション上でデータを変更
しただけ。
4 ¥6,000
残高¥6,000 をデータベースに書き込
む。
Aさんのロックが解放されたので、変
5 ¥6,000
更後の残高¥6,000 を読みとる。
悲観的ロックの特徴は、最初に読みとるときからロックをかけることです。そのため更新処理
は必ず成功します。その代わりロックをかけている時間が長いので、同じデータに対する更新が
同時に発生するシステムでは、ロックの競合が発生し、同時実行性は低下します。
4.2.楽観的ロック(Optimistic Lock)とは
楽観的ロックとは、「自分が操作しているデータは、ほかのユーザーに変更される可能性が少
ない」と考えたロック戦略です。先ほどの例に楽観的ロックを加えます。
残高
1 ¥1,000
Aさん
Bさん
口座番号 10 の残高¥1,000 を読みと
る。
口座番号 10 の残高¥1,000 を読みと
2 ¥1,000
る。
3 ¥1,000
残高に¥5,000 を追加する。この段階で
はアプリケーション上でデータを変更
しただけ。
4 ¥1,000
1で読んだときから、ほかのユーザー
に変更されていないかを確かめる。
5 ¥6,000
変更されていなかったので、残高
¥6,000 をデータベースに書き込む。
6 ¥6,000
残高に¥1,000 を追加する。
7 ¥6,000
2で読んだときから、ほかのユーザー
に変更されていないかを確かめる。
8 ¥6,000
変更されていたので、現在の変更を取
り消し、新しい残高を読みとる。
排他制御の理解とアプリケーションへの適用 26
楽観的ロックの特徴は、ロックをかけている時間が短いことです。この例では4から5の間だ
けロックをかけています。そのためロックが競合する可能性は低く、同時実行性に優れています。
ただし複数のユーザーが同時に同じデータを変更するようなシステムでは、更新に失敗する可能
性が高くなります。また、ほかのユーザーに変更されていることを調べるためのロジックが必要
です。
5.ロック戦略を Oracle に適用する
4章では、楽観的ロックと悲観的ロックの基本的な考え方を解説しました。この章では、それ
を応用し、Oracle に実装する方法を検討します。
5.1.悲観的ロックを Oracle に適用する
これは社員番号 7369 の SMITH さんの給料を変更する例です。
Aさん
Bさん
1 SELECT empno, ename, sal FROM emp
WHERE empno=7369 FOR UPDATE NOWAIT;
EMPNO
ENAME
SAL
------ ---------- -----7369
SMITH
800
2 この間に、アプリケーション上でデータを変
SELECT empno, ename, sal FROM emp
更します。SMITH さんの給料を 300 追加するこ
WHERE empno=7369 FOR UPDATE NOWAIT;
とにします。
行: 1:にエラーが発生しました。ORA-00054:
リソースビジー、NOWAIT が指定されていま
した
3 UPDATE emp SET sal = sal + 300
WHERE empno=7369;
COMMIT;
4
SELECT empno, ename, sal FROM emp
WHERE empno=7369 FOR UPDATE NOWAIT;
EMPNO
ENAME
SAL
------ ---------- -----7369
SMITH
1100
1.社員番号 7369 の行を NOWAIT つきでロック読みとりします。
2.社員番号 7369 の行を NOWAIT つきでロック読みとりします。しかしAさんがロックしている
のでエラーになります。
3.アプリケーション上の変更をデータベースに反映し、変更を確定します。ここでロックが解放
されます。
4.A さんがロックを解放したので、Bさんはロック読み取りが可能になりました。
悲観的ロックでは長時間ロックを保持するので、NOWAIT オプションを使用するほうが一般的
です。ただし相手がロックを解放する直前であってもエラーになるので、「3回リトライしてロ
ックを獲得できなかったらメッセージを表示する」などの仕組みにすることもあります。
27 排他制御の理解とアプリケーションへの適用
5.2.楽観的ロックを Oracle に適用する
楽観的ロックでは、変更したいデータがほかのユーザーに変更されていないことを調べる必要
があります。調べるには次の方法があります。
l
列データをすべて比較する。
l
変更番号を持ち、それを比較する。
それぞれの特徴は次のとおりです。どちらのほうが優れているというものではありません。ア
プリケーションやテーブルの特性に応じて使い分けます。
方式
特徴
列データを比較する
・本来のテーブル構造を変更する必要がない。
・カラム数が多いと比較に時間がかかる。
・レコード長が長いと比較に時間がかかる。特にバイナリーデータ。
・現在の画面で必要とするカラム数よりも表のカラム数が多いときに
は、必要以上のカラムを毎回 SELECT することになり、パフォーマ
ンスが低下する。
変更番号を使う
・変更番号を比較するだけなので簡単。
・変更番号を採番するロジックが必要。
・変更番号を持つカラムを追加する必要がある。つまりそれだけ余分
にディスク領域が必要になる。
表 2 変更検出方式による違い
ここでは列データを比較する方法を紹介します。変更番号を使う方法も、全体の流れは同じで
す。
排他制御の理解とアプリケーションへの適用 28
Aさん
Bさん
1 SELECT * FROM emp
WHERE empno=7369;
EMPNO
ENAME
SAL
------ ---------- -----7369
SMITH
800
2 この間に、アプリケーション上のデータを変
更する。SMITH さんの給料に 300 追加する。
SELECT * FROM emp
WHERE empno=7369;
EMPNO
ENAME
SAL
------ ---------- -----7369
WHERE empno=7369 FOR UPDATE;
ENAME
800
この間に、アプリケーション上のデータを変
3 SELECT * FROM emp
EMPNO
SMITH
更する。SMITH さんの給料を 300 追加する。
SAL
------ ---------- -----7369
SMITH
800
4 行データを比較。
5 UPDATE emp SET sal = sal + 300
WHERE empno=7369;
COMMIT;
6
SELECT * FROM emp
WHERE empno=7369 FOR UPDATE;
EMPNO
ENAME
SAL
------ ---------- -----7369
SMITH
1100
行データを比較。
7
1.社員番号 7369 の行を読み取ります。
2.Aさんはアプリケーション上でデータを変更します。Bさんは社員番号 7369 の行を読み取りま
す。
3.ほかのユーザーに変更されていないことを確認するために、読み取りロックをかけます。
4.1 で読んたデータと、3 で読んだデータを比較します。
5.変更されていなかったので、アプリケーション上の変更をデータベースに反映し、コミットし
ます。
6.こんどはBさんが、ほかのユーザーに変更されていないことを確認するために、読み取りロッ
クをかけます。
7.2 で読んだデータと 6 で読んだデータを比較します。ほかのユーザーに変更されていることがわ
かったので、現在アプリケーション上で加えた変更を破棄し、FOR UPDATE なしで最新のデ
ータを読み取ります(つまり 2 に戻る)。
ここでは NOWAIT オプションを使用していません。楽観的ロックではロックによる待ち時間
が短いため、NOWAIT オプションを使用しないほうが一般的です。ただし、同じ表に対し大規模
なトランザクションを同時に実行する可能性があるときには、NOWAIT オプションを使用し、長
時間の待ち状態を防止します。
29 排他制御の理解とアプリケーションへの適用
6.ロック戦略をアプリケーションに適用する
前章では、悲観的ロックと楽観的ロックの実装方法を解説しました。しかしアプリケーション
にはさまざまな種類や要件があり、この2つの方法を単純に適用できないこともあります。この
章では、実際のアプリケーションにおける排他制御の適用方法について検討します。
アプリケーション(プログラム)を便宜上、次の2つ分類して解説します。
l
1画面に1行のデータを表示するプログラム。
l
1画面に複数行のデータを表示するプログラム。複数行の結果セットを持つプログラム。
6.1.1画面に1行のデータを表示する
「1画面に1行のデータを表示するプログラム」としては、人事システムの個人データ画面や
マスターディテール型の伝票画面などがあります。マスターディテール型の伝票は複数行の明細
データを持っていますが、見出しとなる行は1行だけなので、ここに分類できます。
1画面に1行のデータを表示するので、コミットは行単位(画面)に行います。悲観的ロックを
採用したときのロックのタイミングとして、次の2つが考えられます。
l
はじめにデータを表示するとき。
l
入力項目にキー入力するとき。
これらの方法のメリット・デメリットをまとめたものが次の表です。
コミット
排他
ロックの
コミット
ロ ッ ク
ロ ッ ク
更 新 の
変更
の
制御
タイミン
の
の
の
失敗
チ ェ ッ
グ
タイミン
範囲
期間
狭
長
無
不要
TYPE1
狭
中
無
必要
TYPE2
狭
短
有
必要
TYPE3
単位
分類
ク
グ
1行ずつ
悲観
SELECT
更新/削除
するとき
するとき
キー入力
の瞬間
楽観
更新/削除
更新/削除
するとき
するとき
表 3 排他制御方法の違いによるメリット・デメリット(1画面1行)
TYPE1:はじめにデータを読むときにロックをかけます。はじめからロックをかけているので、
ほかのユーザーに変更されることはありません。したがって変更されているかどうかのチェック
は不要です。1行ごとにコミットするので、この方法が適用できるのは SELECT 対象のデータが
1行のときだけです。複数行の結果セットを持つようなプログラムには適用できません。
TYPE2:データを取りだしたときにはロックをかけず、画面上でユーザーがはじめに入力したと
きにロックをかけます。データを表示してからロックをかけるまでに時間差があるので、変更さ
れているかどうかのチェックは必要です。5章で説明した悲観的ロックと楽観的ロックを組み合
わせたようなロジックになります。
1.行データを読み取る。
2.キー入力があった瞬間、その行にロックをかける。
3.行データが変更されているかどうかを調べる。変更されているときには現在表示されている
データは古いという旨のメッセージを表示し、1に戻る。
4.データベースにコミットする。
排他制御の理解とアプリケーションへの適用 30
TYPE3:画面上で変更を終え、実際にコミットする直前にロックします。ロックしている期間が
短いので同時実行性に優れます。ほかのユーザーに変更/削除されていると、更新に失敗するので、
今回のように入力項目が多いプログラムではあまり使用しません。
6.2.1画面に複数行のデータを表示する
「1画面に複数行のデータを表示するプログラム」としては、マスターメンテナンス画面など
があります。このようなプログラムのキーポイントは、変更したデータをコミットする単位です。
つまり1行ごとにコミットするか、変更した行をまとめてコミットするかです。
1 行ずつコミットするときは、どちらのロック戦略(悲観的・楽観的)も可能で、前節で説明した
方法をそのまま適用できます。複数行まとめてコミットするときは、更新が失敗しないように悲
観的ロックを採用します(失敗したときのコストが大きいため)。
これらの方法のメリット・デメリットをまとめたものが次の表です。
コミット
排他
ロックの
コミット
ロ ッ ク
ロ ッ ク
更 新 の
変更
の
制御
タイミン
の
の
の
失敗
チ ェ ッ
グ
タイミン
範囲
期間
狭
中
無
必要
TYPE4
狭
短
有
必要
TYPE5
広
長
無
不要
TYPE6
狭∼中
中
無
必要
TYPE7
単位
分類
ク
グ
1行ずつ
悲観
楽観
キー入力
・更新/削
の瞬間
除すると
更新/削除
き
するとき
・行移動
するとき
複数行ま
悲観
とめて
SELECT
更新/削除
するとき
するとき
キー入力
の瞬間
表 4 排他制御方法の違いによるメリット・デメリット(1画面複数行)
TYPE4:TYPE2 と同じ。
TYPE5:TYPE3 と同じ。
TYPE6:はじめにデータを読むときにロックをかけます。はじめからロックをかけているので、
他のユーザーに変更されることはありません。したがって変更されているかどうかのチェックは
不要です。ただしロックする範囲が広いので、同時実行性が求められるようなプログラムに向い
ていません。
TYPE7:はじめに読むときにはロックをかけず、変更を加えるたびに行ロックを獲得します。そ
して最終的に確定するときにまとめて更新します。
6.3.どれを選択するべきか?
多くの場合 6.1、6.2 で解説した方法を適用できるはずです。アプリケーションの特性やそれぞ
れの特性を考え、最適な方法を選択してください。もちろんアプリケーションの要件によっては
他の方法が適切なこともあります。そのときにはさまざまな観点からメリット・デメリットを考
え、アプリケーションの要件を満たす排他制御方法を選択してください。
31 排他制御の理解とアプリケーションへの適用
7.参考文献
1)「Oracle8 Server 概要 リリース 8.0」,日本オラクル,1997
2)「Oracle8 Server アプリケーション開発者ガイド リリース 8.0」,日本オラクル,1997
3)「Oracle8 Server SQL リファレンス リリース 8.0」,日本オラクル,1997
4)「Oracle8 Server リファレンス リリース 8.0」,日本オラクル,1997
5)「Oracle8 Server 管理者ガイド リリース 8.0」,日本オラクル,1997
6)「Oracle Net8 リリース 8.0 管理者ガイド」,日本オラクル,1997
この文書はあくまでも参考資料であり、掲載されている情報は予告なしに変更されることがあ
ります。この文書に関連して不都合が生じた場合も、米国オラクル社及び日本オラクル株式会社
は一切保証せず、特に責任は負いかねますのでご容赦ください。また許可なく、改編、引用する
ことを禁じます。
1998 年 11 月 24 日 初版
日本オラクル株式会社 Design & Migration Services グループ
Copyright© ORACLE CORPORATION JAPAN 1998
排他制御の理解とアプリケーションへの適用 32