以前から少し気になっていたので、以下のマニュアルの記述について、実際の動作を確認してみました。
アプリケーション・ビルダー・ユーザーズ・ガイド Release 20.2
翻訳の問題ではないことを確認するため、英語の方も参照しています。
App Builder User's Guide
調べた結果としていえることは、マニュアルのDMLロックについてとして記載されている内容は、以前の静的コンテンツのリージョンを使ったフォームでのプロセス、タイプは行の自動処理(DML)[レガシー]にのみ有効な記述で、新しいフォーム・リージョンや対話グリッドはこの設定を参照していません。
動作を確認するために、SQLワークショップのサンプル・データセットからEMP/DEPTをインストールし、そのままアプリケーションを作成しました。
言語にJapaneseを選んでいると、作成されるアプリケーションの名前はデモ - 従業員/部門となるでしょう。何も変更せず、アプリケーションの作成を実行します。
これで動作確認に使用するアプリケーションは完成しました。
APEX_DML_LOCK_WAIT_TIMEの動作について確認します。
マニュアルの記載によると、この値が定義されていないときは無期限にロックの取得を待つ、となっています。
NULL (the default), results in the same behavior as previous versions of Oracle Application Express, that is, wait indefinitely.
アプリケーションは作成したばかりなので、APEX_DML_LOCK_WAIT_TIMEは設定していません。
アプリケーションを実行します。従業員のレポートを開きます。
任意の従業員を選んで、編集フォームを開きます。
SQL> select * from emp for update;
フォームで変更の適用をクリックすると、ORA-00054 リソース・ビジー。NOWAITが指定されているか、タイムアウトしました、とメッセージが表示され、変更の適用は行われません。
実際にデバッグ・レベルを完全トレースまであげ、発行されているSQLを確認しても、for update nowaitとなっています。
アプリケーション置換文字列、アプリケーション・アイテムまたはページ・アイテムとして、APEX_DML_LOCK_WAIT_TIMEを設定することができる、と記載されているので、アプリケーション置換文字列として10秒を設定してみます。
同じ操作を行って動作の確認をします。その前にAPEX_DML_LOCK_WAIT_TIMEに設定した値がプロセスに渡っていることを、エラーが発生したときに確認できるよう、エラー・メッセージを以下に変更します。
待ち時間は&APEX_DML_LOCK_WAIT_TIME.秒にしています。
結果は変わらず、ORA-54が発生します。エラー・メッセージには待ち時間は10秒にしています。と表示されているため、APEX_DML_LOCK_TIME_WAITの値はプロセスに渡っています。
レガシーのフォームでの動作を確認するために、アプリケーション・ギャラリよりSample Database Applicationをインストールします。
サンプルとなるアプリケーションにレガシーな機能を使っているのか、というのはさておき、インストールしたアプリケーションの置換文字列として、APEX_DML_LOCK_TIME_WAITを10に設定しておきます。
アプリケーションを実行し、顧客の一覧から任意の顧客を選んで編集画面を開きます。
sqlplusより表DEMO_CUSTOMERSの排他ロックを取得します。
SQL> select * from demo_customers for update;
フォームのApply Changesをクリックすると10秒待ち、エラー・メッセージとしてresource busy; acquire with WAIT timeout expiredが表示されます。
マニュアルに記載されているとおりの動作です。
FSP_DML_LOCK_ROWについては、レガシーのプロセスであっても、行のロックとしてGUIから設定できるため、まったく使う機会はないでしょう。
Oracle APEXでは、ロストアップデートの保護のために、行のチェックサムやバージョンを取得する際にSELECT文を発行します。以前はFSP_DML_LOCK_ROWやAPEX_DML_LOCK_TIME_WAITの指定を参照して、for update wait 10といった調整ができましたが、新しいフォームおよび対話グリッドではfor udpate nowaitを発行し、待機せず、取得できなければエラーとする動作になっているようです。
どちらの設定でも一長一短があり、アプリケーションの用途に応じて設定を変える内容かと思います。オンライン操作でそれほど処理の競合が発生するとは思えず、また、バッチ処理などが同時に実行されているような場合は、明らかにnowaitが有利だと思います。
そうでない場合は、例えば、今回の例のように待ち時間10秒でロック待機するPL/SQLコードを行のロックに記述すればいいのかな?と思ったのですが、、、
実際に行のロックに設定すると、ずっと待機します。
待機中のSQLを確認したところ、自動DMLプロセスが発行したUPDATE文で待機が発生していて、行のロックとして記述したPL/SQLコードではありません。
SQL> select sql_text from v$sql
2 where sql_id in
3 (
4 select sql_id from v$session
5 where sid in
6 (
7 select waiting_session from dba_waiters
8 )
9 );
SQL_TEXT
--------------------------------------------------------------------------------
update "EMP" set "ENAME"=:APEX$VAL2,"JOB"=:APEX$VAL3,"MGR"=:APEX$VAL4,"HIREDATE"
=:APEX$VAL5,"SAL"=:APEX$VAL6,"COMM"=:APEX$VAL7,"DEPTNO"=:APEX$VAL8 where "EMPNO"
=:APEX$PK1
update "EMP" set "ENAME"=:APEX$VAL2,"JOB"=:APEX$VAL3,"MGR"=:APEX$VAL4,"HIREDATE"
=:APEX$VAL5,"SAL"=:APEX$VAL6,"COMM"=:APEX$VAL7,"DEPTNO"=:APEX$VAL8 where "EMPNO"
=:APEX$PK1
SQL>
海外のブログ記事でも、ずっと以前ですが同様の指摘をしている人を見つけました。