Oracleで名前付きロック(アドバイザリロック)を現場で使う必要があったので、忘備録です。
具体的には、DBMS_LOCKパッケージを使った名前付きロックの取得と解放のやり方をメモします。
Contents
SQLの例
早速、SQLの例は以下のようになります。
DECLARE
my_lockhandle VARCHAR2(128);
my_result INTEGER;
BEGIN
-- ユニークなロックIDを取得
DBMS_LOCK.ALLOCATE_UNIQUE(lockname => 'MY_LOCK', lockhandle => my_lockhandle);
DBMS_OUTPUT.PUT_LINE('作られたロックハンドル: ' || my_lockhandle);
-- ロックを取得
my_result := DBMS_LOCK.REQUEST(lockhandle => my_lockhandle, lockmode => DBMS_LOCK.X_MODE, timeout => 10);
IF my_result = 0 THEN
DBMS_OUTPUT.PUT_LINE('ロックの取得に成功');
-- ここで排他処理を実行
-- ロックを解放
my_result := DBMS_LOCK.RELEASE(lockhandle => my_lockhandle);
IF my_result = 0 THEN
DBMS_OUTPUT.PUT_LINE('ロックの解放に成功');
ELSE
DBMS_OUTPUT.PUT_LINE('ロックの解放に失敗、、、');
END IF;
ELSE
DBMS_OUTPUT.PUT_LINE('ロックの取得に失敗、、、');
END IF;
END;
/
1つずつ見ていきましょう
DBMS_LOCK.ALLOCATE_UNIQUE() プロシージャ
6行目のユニーク(UNIQUE)な名前を割り当て(ALLOCATE)たロックの情報を(無ければ作ってから)取得しています。ただ取得しただけで、まだロックを「行使」していません。
-- ユニークなロックIDを取得
DBMS_LOCK.ALLOCATE_UNIQUE(lockname => 'MY_LOCK', lockhandle => my_lockhandle);
ここで、DBMS_LOCK.ALLOCATE_UNIQUE()
プロシージャの定義は以下のようになります。
DBMS_LOCK.ALLOCATE_UNIQUE(
lockname IN VARCHAR2,
lockhandle OUT VARCHAR2,
expiration_secs IN INTEGER DEFAULT 864000
);
ここで、INパラメータの「lockname」にはお好きなロックの名前 (予約されてる「ORA$」で始まる名前はダメ) を、
「expiration_sesc」 (expiration seconds=期限切れの秒数) は何秒後に(DBMS_LOCK_ALLOCATED
表というところに)登録されたそのロック情報が消せるようにするか、です。デフォルト値は10日 (=864000秒) です。(その後にDBMS_LOCK.ALLOCATE_UNIQUE()
が呼び出されたときに消されます。)
OUTパラメータの「locakhandle」は、11行目、ロックの「REQUEST(要求する)」に使います。
DBMS_LOCK.REQUEST() ファンクション
-- ロックを取得
my_result := DBMS_LOCK.REQUEST(lockhandle => my_lockhandle, lockmode => DBMS_LOCK.X_MODE, timeout => 10);
ここで、DBMS_LOCK.REQUEST()
ファンクションの定義は以下のようになります。
DBMS_LOCK.REQUEST(
id IN INTEGER || lockhandle IN VARCHAR2,
lockmode IN INTEGER DEFAULT X_MODE,
timeout IN INTEGER DEFAULT MAXWAIT,
release_on_commit IN BOOLEAN DEFAULT FALSE
)
RETURN INTERGER ;
ここで、1つめのINパラメータ は「id」または「lockhandle」で、ふつうは先ほどのALLOCATE_UNIQUE()
のOUTの「lockhandle」を指定するでしょう。(ちなみに「id」はDBMS_LOCK_ALLOCATED
表というのを見ればわかります。)
2つめの「locakmode」では、「排他ロック」: X_MODE や「共有ロック」: S_MODE などのあらかじめ定義された定数を指定します。(ほかにもありますが、この2つで十分なことが多いと思います。「排他ロック」と「共有ロック」の違いについてはここで触れません。)
3つめはロックを取得をしようとするのに対してのtimeout(タイムアウト)を秒単位で指定、
4つめのBOOLEANはコミットまたはロールバックしたときに(COMMIT()
またはROLLBACK()
を呼び出したときに)ロックを解放するかどうか(デフォルトはFALSE)です。
これがFALSEでしかも明示的に解放されなかった(下記、RELEASE()
が呼び出されなかった)としても、セッションが終われば解放されます。
戻り値のINTEGER(整数)は、0~5で、以下です。
- 0 : 取得成功
- 1 : タイムアウト
- 2 : デッドロック発生!
- 3 : パラメータエラー
- 4 : すでにその「lockname」のロックを取得済み
- 5 : 不正なロック操作
DBMS_LOCK.RELEASE() ファンクション
「locakhandle」 は19行目、ロックの「RELEASE(解放する)」にも使います。
-- ロックを解放
my_result := DBMS_LOCK.RELEASE(lockhandle => my_lockhandle);
ここで、DBMS_LOCK.RELEASE()
ファンクションの定義は以下のようになります。
DBMS_LOCK.RELEASE(
id IN INTEGER || lockhandle IN VARCHAR2
)
RETURN INTERGER ;
INパラメータ 「id」または「lockhandle」で、REQUEST()
と同様です。
戻り値のINTEGER(整数)は、0, 3, 4, 5で、以下です
- 0 : 解放成功
- 3 : パラメータエラー
- 4 : その「lockname」のロックを取得していない
- 5 : 不正なロック操作
何か間違いがありましたらコメントを頂ければ幸いです。
(参考: https://docs.oracle.com/cd/E57425_01/121/ARPLS/d_lock.htm)