ここではCachedUpdatesについて説明します。
Delphi Professional 以上のユーザであれば、BDE(Borland Database
Engine)で「キャッシュアップデート」という機能が使えるということを聞いたことがあるはずです。しかし、Delphiのマニュアルの説明不足のせいで、よく知らないために使わないでいる方も多いと思います。
通常は、データセット上のレコードを更新して、Postメソッドで更新を確定させると、その場でOracleサーバに対しUPDATE/INSERT/DELETE文が発行されますが、
TOracleDataSetがCachedUpdatesモードに設定されている場合は、Postメソッドによる更新の確定は、クライアントアプリケーションの内部に設けられたローカルの更新キャッシュに対して行われ、SQLの発行が(後回しに)遅延されます。
実際にOracleサーバ上のデータを更新するには、TOracleDataSetのApplyUpdatesメソッドを呼び出します。このとき、ローカルの更新キャッシュに蓄積されていた変更が、一括してOracleサーバに受け渡されます。
-
TOracleDataSet
- property
CachedUpdates:
boolean:
Trueにセットすると、データセットの更新をローカルにキャッシュします。
- property
- TOracleSession * procedure
ApplyUpdates(const
DataSets: array of TOracleDataSet;
Commit: Boolean) :- データセットのローカル更新キャッシュに行われた更新内容を、SQLサーバに反映させます
- master/detail
関係が定義されている場合、masterデータセットについてApplyUpdatesを行うことで、detailデータセットの更新キャッシュも反映されます - さらに、Commit=Trueの場合は、トランザクションをコミットします
- procedure
CommitUpdates(DataSets): ApplyUpdates(DataSets, True)と同じです - procedure
CancelUpdates(const DataSets: array of TOracleDataSet):- データセットのローカル更新キャッシュをクリアします
- 変更されていたレコードは元に戻り、挿入されていたレコードは消え、削除されていたレコードは復活します
CachedUpdatesのメリット
- サーバに負荷をかけないトランザクション制御
- CachedUpdatesモードでは、CancelUpdatesによって変更を取り消すことが可能です。そのため、ApplyUpdates/CancelUpdatesを使ってトランザクション制御が実現できます。しかも、Commit/Rollbackと異なり、CachedUpdates/CancelUpdatesは、SQLサーバの資源を全く消費しない点ですぐれています
- バッチ:パフォーマンスの向上
- 一度に多数のレコードを更新する場合には、クライアントアプリケーションと、Oracleサーバの間の通信の発生回数を抑えることで、通信のオーバヘッドにかかる時間を節約することができます
- オンライン:トランザクション継続時間の短縮
- 一般に、「長い」トランザクションによって、長時間にわたりレコードをロックすることは、レコードロックの競合を起こりやすくするため、デッドロックや、応答時間低下の原因となり得ます。CachedUpdatesを使うと、トランザクションの開始がApplyUpdatesが行われるまで遅延されるため、「長い」トランザクションが発生しにくくなります
CachedUpdatesのデメリット
- バッチ:ローカルシステム資源の消費
- ローカルに更新キャッシュを保持するためのメモリが必要なため、あまりに大量のレコードを一度に更新すると、メモリが不足する恐れがあります。一般的に言って、CachedUpdatesでキャッシュするレコード件数が、数十万件になるようなら、途中で更新を分割してApplyUpdatesするべきでしょう
- オンライン:最新レコードの反映遅れ
- CachedUpdatesの性質上、どこかの端末で更新した最新データは、すぐにOracleのデータベースに反映されず、しばらくはその端末のローカルキャッシュに留まります。したがって、不特定多数のユーザによって更新され、つねに最新データの参照が必要であるようなデータを扱う場合には、CachedUpdatesによって更新を長時間キャッシュするのは避けるべきです。
CachedUpdatesの使用例: master/detail関係にあるテーブルの挿入
例えば、受注(Order)テーブルと、受注詳細(OrderDetail)テーブルという二つのテーブルに、注文書の内容を挿入するとします
- 注文番号を新たに採番する
- 受注テーブルに1レコードを挿入し、(注文番号、顧客番号と、注文日付)をセット
- 受注詳細テーブルに、受注した各製品ごとにレコードを挿入し(注文番号、注文書行番号、製品番号、受注数量)をセット
というシナリオを処理するオンラインアプリケーションを考えてみて下さい。注文詳細テーブルを1レコード更新するごとに、Oracleに更新を反映する必要は全くありません。データの更新は、注文書作成が完了した時に1回でいいはずです。
- 二つのテーブルそれぞれに挿入するデータを編集するために二つのデータセットを用意します
//OrderQuery.SQL = 'SELECT Order.ROWID, Order.* FROM Order WHERE 0=1' //OrderDetailQuery.SQL = 'SELECT OrderDetail.ROWID, OrderDetail.* FROM OrderDetail WHERE 0=1'
注文入力フォームを開いたらまず上記の例のようにWHERE条件をFalseのままにOrderQueryを開き、即座にAppendして、空のレコードを作ります。これをデータベース対応コンポーネントでエディットするとよいでしょう。
顧客番号を入力するEditBox(実際の業務の場合は50音順検索やリストからの選択機能が必要でしょう)があり、DetailQueryはDBGridコンポーネントに関連付けられていて、製品番号と受注数量の一覧編集を行うことでしょう。
…と、前置きが長くなりましたが、「注文入力」ボタンのコードは例えばこんな感じです。
//OrderQuery.CachedUpdates = True //OrderDetailQuery.CachedUpdates = True procedure SubmitButtonClick(Sender: TObject); var NewOrderNumber: integer; i: integer; begin //まず、注文番号を採番 SequenceQuery.SQL = 'SELECT OrderNumSeq.NextVal NewOrderNumber FROM DUAL'; SequenceQuery.Open; NewOrderNumber := SequenceQuery.Field('NewOrderNumber').AsInteger; //受注テーブルにキー項目をセット with OrderQuery do begin Edit; Field('OrderNumber').AsInteger := NewOrderNumber; Post; end; //受注詳細テーブルのキー項目をセット with OrderDetailQuery do begin i := 0; First; while not Eof do begin Edit; Field('OrderNumber').AsInteger := NewOrderNumber; Field('OrderDetailNumber').AsInteger := i; Post; Next; Inc(i); end; end; //ここまでの更新は、ローカルにキャッシュされる //ここでまとめて更新をサーバに反映する OracleSession.ApplyUpdates([OrderQuery, OrderDetailQuery], True); //(...以下省略...たとえばフォームを閉じたり、クリアしたり…) end;