2.3.4 排他制御

排他制御では,レコードやテーブルのロック方法を設定したり,データの入力方法までも設定します。この排他制御は,データの整合性やアプリケーションの実行性能に影響するものであり,正しく理解して利用することが必要です。特に,性能を重視する場合,ロックする対象や範囲を必要最小限とする必要があります。

また,利用するデータベースによって,この排他制御の機能や動作が異なりますので,注意が必要です。

<この項の構成>
(1) レコード排他の種類
(2) HiRDB,XDM/RDの排他
(3) ORACLEの排他
(4) SQL Anywhere,Adaptive Server Anywhereの排他
(5) SQL Serverの排他
(6) SQL/Kの排他
(7) UPDATE,DELETE,INSERT文によるロック
(8) LOCK文によるテーブルのロック
(9) 排他エラーとデッドロック
(10) TPBrokerのOTS機能を利用するケースの排他

(1) レコード排他の種類

(a) 排他の設定方法

簡易クラスでは,ResultSetに検索結果を得る時,DBRResultSetクラスのExecuteメソッドの引数で排他の種類を設定します。

詳細版クラスでは,ResultSetに検索結果を得る時,SetResultSetTypeメソッドの引数で排他の種類を設定します。

(b) 排他の種類

ExecuteメソッドやSetResultSetTypeメソッドで設定する排他の種類と目的について説明します。

(c) ロックの有効範囲

簡易クラスでは,Executeメソッドでロックの方法を設定した場合に,ロック制御されるのは,Openメソッド実行時点から,コミット,又はロールバック実行終了までの間です。詳細版クラスでは,GetResultSetメソッド実行時点から,コミット又はロールバック実行終了までロック制御されます。

(2) HiRDB,XDM/RDの排他

HiRDB,XDM/RDでは,排他の種類ごとに下記オプションが付加されます。

(3) ORACLEの排他

ORACLEの場合,排他の種類ごとに下記オプションが付加されます。

ORACLEの場合,TYPE EXCLUSIVEだけですが,SQLのLOCK文を実行(ExecuteDirectメソッド)することによっても,レコードのロック方法やテーブルのロック方法を設定できます。

下記にLOCK文で設定できる排他オプションの概略について説明します。詳細については,ORACLEのドキュメントを参照してください。

(4) SQL Anywhere,Adaptive Server Anywhereの排他

このデータベースの場合,排他の種類ごとに下記オプションが付加されます。

SQL Anywhere,Adaptive Server Anywhereの場合,SQLのSET OPTION文を実行(ExecuteDirectメソッド)することによっても,レコードのロック方法を設定できます。

下記にSET OPTION文で設定できる排他オプションの概略について説明します。詳細については,データベースのドキュメントを参照してください。また,ロックモードを切り替える場合には,再度SET OPTION文を実行する必要があります。

オプションの詳細については,SQL Anywhereのドキュメントを参照してください。

(5) SQL Serverの排他

SQL Serverの場合,排他の種類ごとに下記オプションが付加されます。

(6) SQL/Kの排他

SQL/Kの場合,排他の種類ごとに下記オプションが付加されます。

SQL/Kの場合,SQLのSELECT文にFOR UPDATE句を指定することによっても,レコードのロック方法を設定できます。この場合,資源の競合時に共用しない設定にしていると,競合が解除されるまで待ち状態になります。

(7) UPDATE,DELETE,INSERT文によるロック

簡易クラスでは,DBRDatabaseクラスのExecuteDirectメソッドにUPDATE文,DELETE文,INSERT文を使って更新・削除・追加することができます。

詳細クラスでは,DBStatementクラスのExecuteメソッドなどにUPDATE文,DELETE文,INSERT文を使って更新・削除・追加をすることができます。

UPDATE文,DELETE文,INSERT文の場合には, ExecuteDirectやExecuteメソッド実行開始からコミット,又はロールバック実行時点までの間,更新・削除・追加されたレコードがロックされます。

このUPDATE文,DELETE文で更新・削除対象のレコードは,いったん検索してから更新・削除されますので検索時はロックによる待ちが発生します。

SQL Serverの場合は付属のドキュメントを参照してください。

(8) LOCK文によるテーブルのロック

簡易クラスの場合,DBRDatabaseクラスのExecuteDirectメソッドでLOCK文を実行することにより,テーブルをロックすることができます。

詳細クラスの場合,DBConnectionオブジェクトのExecuteDirectメソッド,DBStatementオブジェクトのExecuteメソッド,DBPreparedStatementオブジェクトのExecuteメソッドからLOCK文を実行することにより,テーブルをロックすることができます。

テーブルのロック範囲は,LOCK文を実行した時点から,コミット,又はロールバック実行終了までの間です。

LOCK文でテーブルをロックすると,レコード単位のロックをしないため,オーバヘッドが大幅に削減できますが,同時に動作できるアプリケーションの多重度が下がります。

テーブル単位にロックを掛けるためには,LOCK文でテーブルをEXCLUSIVEモードとします。

(例)hirdb.ExecuteDirect("LOCK TABLE TABLE1 IN EXCLUSIVE");

SQL Serverでは,SELECT文の排他オプションを使ってテーブルをロックし,詳細版クラスの場合は,SetResultSetTypeメソッドの設定でテーブルをロックすることもできます。「2.3.3(5)SQL Serverの排他」を参照してください。

SQL Anywhere,Adaptive Server Anywhereの場合はテーブルのロックは使用できません。

(9) 排他エラーとデッドロック

(a) 排他エラー時の動作

ロックを掛けようとしたときに,ほかのトランザクションによって該当するレコードやテーブルがロックされていた場合の動作として,ロックが解除されるまで待つか,エラーを報告するかのどちらかの動作を選択できます。この選択は,データベース接続時にConnectメソッドの引数で次の指定をします。

これ以外に,ロックされていたときのエラースロー時に,ロールバックするかどうかを指定する, LOCK_OPT_WITH_ROLLBACK, LOCK_OPT_WITHOUT_ROLLBACKがありますが,使用するDBMSによって動作が異なるため,詳細については「4.簡易版関数詳細」又は「5.詳細版関数詳細」のConnectメソッドを参照してください。

簡易版クラスでは,DBRDatabseクラスのConnectメソッドで指定した値は,そのコネクションでのデフォルト値となります。また,ロックされていたときの動作は,DBRResultSetクラスのExecuteメソッドの実行時にも指定できます。実行時に特に指定しない場合は,DBRDatabaseクラスのConnectメソッドで指定した値が有効になります。

詳細版クラスでは,DBDriverクラスのConnectメソッドで指定した値がデフォルト値となります。また,ロックされていたときの動作は,DBStatementクラスのExecuteメソッド,又はDBPreparedStatementクラスのExecuteメソッドの実行時にも指定できます。実行時に特に指定しない場合は,Connectメソッドで指定した値が有効になります。

一般的なアプリケーションでは,LOCK_OPT_WAITと設定し,ロックエラーを意識しないで,デッドロックだけを意識したコーディングにします。LOCK_OPT_NOWAITを使用するアプリケーションでは,個々のデータベースアクセスごとに一定時間を空けてリトライをするなどのコーディングが余計に必要となります。

SQL Serverの場合,引数の指定は無効です。ロックが解除されるまで待ちます。

(b) 排他エラーとデッドロックの判定

排他エラー時の動作で,LOCK_OPT_WAITを指定した場合,ほかのトランザクションとの間でデッドロックが発生することがあります。この場合,DBMSからエラーが返され,DABroker for C++ではDB_ERROR_DRIVER_ERRORをスローします。

トランザクション処理中にエラーがスローされたとき,DBSQLCAクラスのe_USERCODE,又はe_USERERRORメソッドに次に示す値が設定されます。

ただし,RDA Link for Gatewayを経由してメインフレーム系のデータベースをアクセスする場合には,e_USERCODEにエラーが返らないので注意が必要です。

(c) デッドロック時の対応

アプリケーションでは,デッドロックが発生した場合に,どのように対処するかを決めておく必要があります。

基本的には,デッドロックが発生したときには,デッドロックが通知されたトランザクションは,ロールバックしてそのロックを解除することで,デッドロックを回避します。

その後の処理方法は,アプリケーションの形態によって異なりますが,特にバッチ処理,又はWebから起動されるアプリケーションの場合は,次のような処理をするのが一般的です。

  1. デッドロックを検知したため,ロールバックを実行してロックを解除します
  2. 一定時間を置いて,再度実行します
  3. 一定の回数,繰り返して同様のエラーが起こるようであれば,アプリケーションの処理を終了させます

デッドロックが発生した場合のコーディング例については,「2.4 エラー処理」を参照してください。

(d) 効率を考えた参照と更新

複数のサーバアプリケーションからDABroker及びデータベースにアクセスする場合は,使用するデータベースの排他範囲を最小限に押さえたり,データベースとの連絡回数を削減することを考慮しておく必要があります。

参照してから更新するような場合,次のような方法があります。

  1. 更新したいデータを,まず参照専用のResultSetで検索し,排他制御は掛けません。
  2. 検索したデータの中から更新に必要なレコードを確認します。
  3. 更新対象のレコードだけを更新可能なResultSetで検索し,ロックを掛けます。
  4. レコードを更新します

必要なレコードだけにロックが掛かるので,ほかのトランザクションからのアクセスへの排他を最小限に押さえられます。

(10) TPBrokerのOTS機能を利用するケースの排他

TPBrokerのOTS機能を利用し,複数のDBMSをアクセスする場合,排他制御の範囲はDBMS単位です。複数のDBMSにまたがる排他制御は行われません。つまり,一つのDBMS内でのデッドロックは検出されますが,複数のDBMS間にまたがる待状態は検出されないことになります。このため,複数のDBMS間でデッドロックが発生するようなアプリケーションは設計してはなりません。