一部のOracle Streams環境では、複数のデータベース間にデータ共有が原因で発生する可能性のあるデータの競合を解消するために、競合ハンドラを使用する必要があります。
この章の内容は次のとおりです。
競合は、LCR内の古い値と、表内の予期されるデータが一致しないことです。複数データベース上で同じデータに対して同時のデータ操作言語(DML)操作を許可しているOracle Streams環境では、競合が発生する可能性があります。Oracle Streams環境でDMLの競合が発生する可能性があるのは、適用プロセスがDML操作によって生じる行の変更を含むメッセージを適用している場合のみです。この種のメッセージは、行の論理変更レコードまたは行LCRと呼ばれます。適用プロセスでは、行LCRが原因で発生する競合が自動的に検出されます。
たとえば、異なるデータベースで発生した2つのトランザクションによって同じ行がほぼ同時に更新されると、競合が発生する場合があります。Oracle Streams環境の構成時には、競合の発生を許可するかどうかを考慮する必要があります。システム設計で競合が許可されている場合は、競合を自動的に解消するように競合解消を構成できます。
通常は、競合の可能性を回避するOracle Streams環境を設計する必要があります。この章で後述する競合防止技法を使用すると、ほとんどのシステム設計で共有データ全体または大部分における競合を防止できます。ただし、多くのアプリケーションでは、複数のデータベースで共有データをある程度の割合まで随時更新可能にする必要があります。この場合は、競合の可能性への対処が必要となります。
|
注意: 適用プロセスでは、DDLの競合またはユーザー・メッセージによって発生する競合は検出されません。この種の競合が環境で防止されることを確認してください。 |
|
参照: 行LCRの詳細は、『Oracle Streams概要および管理』を参照してください。 |
複数のデータベースでデータを共有する場合は、次のタイプの競合が発生する可能性があります。
更新の競合が発生するのは、適用プロセスで適用される行LCRに含まれている行の更新が、同じ行の他の更新と競合する場合です。また、異なるデータベースからの2つのトランザクションによって、同じ行がほぼ同時に更新される場合にも、更新の競合が発生する可能性があります。
一意性競合が発生するのは、適用プロセスで適用される行LCRに含まれている行の変更が、主キー制約や一意制約など、一意性の整合性制約に違反している場合です。たとえば、2つの異なるデータベースからの2つのトランザクションがあり、それぞれのトランザクションが同じ主キー値を持つ表に1行を挿入する場合を考えます。この場合は、この2つのトランザクションによって一意性競合が発生します。
削除の競合が発生するのは、異なるデータベースで発生した2つのトランザクションがあり、1つのトランザクションで1行を削除し、もう1つのトランザクションで同じ行を更新または削除する場合です。この場合、行LCR内で参照された行は存在せず、更新も削除もできません。
外部キーの競合が発生するのは、適用プロセスで適用される行LCRに含まれている行の変更が、外部キー制約に違反している場合です。たとえば、hrスキーマ内で、employees表のdepartment_id列は、departments表のdepartment_id列の外部キーです。次の変更が2つの異なるデータベース(AとB)で発生し、3つ目のデータベース(C)に伝播される場合を考えます。
データベースAで、department_idが271の1行がdepartments表に挿入されます。この変更はデータベースBに伝播され、そこで適用されます。
データベースBで、employee_idが206、department_idが271の1行がemployees表に挿入されます。
データベースBで発生した変更がデータベースAで発生した変更よりも先にデータベースCで適用されると、データベースCのdepartments表にはまだdepartment_idが271の部門の行が存在しないため、外部キーの競合が発生します。
Oracle Streams環境では、3つ以上のデータベースがデータを共有し、そのうち2つ以上のデータベースでデータが更新されると、順序付けの競合が発生する可能性があります。たとえば、3つのデータベースがhr.departments表の情報を共有している使用例を考えます。データベースの名前はmult1.net、mult2.netおよびmult3.netです。mult1.netでhr.departments表の1行が変更され、その変更がmult2.netとmult3.netの両方に伝播されると想定します。次の一連のアクションが発生する可能性があります。
変更がmult2.netに伝播します。
mult2.netの適用プロセスがmult1.netからの変更を適用します。
mult2.netで、同じ行に対して別の変更が行われます。
mult2.netでの変更がmult3.netに伝播します。
mult3.netで、1つの適用プロセスは、別の適用プロセスがmult3.netでmult1.netから変更の適用を実施する前に、mult2.netから変更の適用を試みます。
この場合、mult3.netでの行の列値はmult2.netから伝播した行LCR内の対応する元の値と一致しないため、競合が発生します。
トランザクションが順不同で適用されると、データ競合の原因となるのみでなく、データのサポートがリモート・データベースに正常に伝播されていないと、そのデータベースで参照整合性の問題が発生する場合があります。新規の顧客が受注部門に連絡する場合の使用例を考えます。顧客レコードが作成され、受注が処理されます。リモート・データベースで受注データが顧客データより前に適用されると、受注で参照されている顧客はリモート・データベースに存在しないため、参照整合性エラーが発生します。
順序付けの競合が発生した場合は、必要なデータがリモート・データベースに伝播して適用されてから、エラー・キューにあるトランザクションを再実行すれば、競合を解消できます。
適用プロセスでは、更新、一意性、削除および外部キーの競合が次のように検出されます。
行LCR内の行の元の値と、接続先データベースにある同じ行の現行の値が一致しない場合は、適用プロセスによって更新の競合が検出されます。
挿入または更新操作を含むLCRの適用時に一意制約違反が発生すると、適用プロセスによって一意性競合が検出されます。
更新または削除操作を含むLCRの適用時に、行の主キーが存在しないためにその行が見つからないと、適用プロセスによって削除の競合が検出されます。
LCRの適用時に外部キー制約違反が発生すると、適用プロセスによって外部キーの競合が検出されます。
適用プロセスでLCRを直接適用しようとするか、DMLハンドラなどの適用プロセス・ハンドラでLCRに対してEXECUTEメンバー・プロシージャを実行すると、競合が検出される場合があります。また、DBMS_APPLY_ADMパッケージのEXECUTE_ERRORまたはEXECUTE_ALL_ERRORSプロシージャの実行時にも、競合が検出される場合があります。
|
注意:
|
デフォルトでは、適用プロセスは競合検出中にすべての列の古い値を比較しますが、DBMS_APPLY_ADMパッケージのCOMPARE_OLD_VALUESプロシージャを使用すると、非キー列の競合検出を停止できます。競合検出は、一部の非キー列では不要な場合があります。
この項では、データ競合を防止する方法について説明します。
システム内で、共有データを含む表への同時更新アクセスを行うデータベースの数を制限することによって、競合の可能性を回避できます。プライマリ所有権を使用すると、共有データ・セットへの更新が1つのデータベースにしか許可されないため、すべての競合が防止されます。アプリケーションでは、行と列のサブセットを使用して、データの所有権を表レベルよりも細かく設定できます。たとえば、アプリケーションでは、共有表の特定の列や行への更新アクセス権を、データベースごとに設定できます。
プライマリ・データベース所有権モデルではアプリケーション要件にとって限定的すぎる場合は、共有所有権データ・モデルを使用できます。このデータ・モデルは、競合が発生する可能性があることを意味します。それでも、通常は単純な方針をいくつか使用して特定タイプの競合を防止できます。
各データベースで共有データに一意識別子を使用させることで、一意性競合を回避できます。Oracle Streams環境ですべてのデータベースに一意識別子を確実に使用させるには、3つの方法があります。
その1つは、次のSELECT文を実行して一意識別子を構成することです。
SELECT SYS_GUID() OID FROM DUAL;
このSQL演算子は、16バイトのグローバル一意識別子を戻します。この値は、グローバル一意識別子を生成するために時刻、日付およびコンピュータ識別子を使用するアルゴリズムに基づいています。グローバル一意識別子は、次のような書式で表示されます。
A741C791252B3EA0E034080020AE3E0A
また、データを共有する各データベースで順序を作成し、データベース名(または他のグローバル一意値)をローカルの順序と連結して一意性競合を防止する方法もあります。このアプローチを使用すると、重複する順序値を回避し、一意性競合を防止できます。
最後に、2つのデータベースで同じ値を生成できないように、データを共有する各データベースでカスタマイズされた順序を作成する方法があります。そのためには、CREATE SEQUENCE文で開始値、増分値および最大値の組合せを使用します。たとえば、次の順序を構成できます。
表3-1 Oracle Streamsレプリケーション環境用にカスタマイズされた順序
| パラメータ | データベースA | データベースB | データベースC |
|---|---|---|---|
|
|
1 |
3 |
5 |
|
|
10 |
10 |
10 |
|
範囲の例 |
1, 11, 21, 31, 41,... |
3, 13, 23, 33, 43,... |
5, 15, 25, 35, 45,... |
同様のアプローチを使用すると、データベースごとに一意の範囲を生成するSTART WITHおよびMAXVALUEを指定して、各データベースに異なる範囲を定義できます。
更新の競合が検出された場合、競合ハンドラではその解消を試みることができます。Oracle Streamsには、更新の競合を解消するためのビルトイン競合ハンドラが用意されていますが、一意性、削除、外部キーまたは順序付けの競合については用意されていません。ただし、ビジネス・ルールに固有のデータ競合を解消するために、独自のカスタム競合ハンドラを作成できます。この種の競合ハンドラは、DMLハンドラまたはエラー・ハンドラに付属させることができます。
ビルトイン競合ハンドラとカスタム競合ハンドラのどちらを使用する場合も、競合が検出されるとただちに使用されます。指定した競合ハンドラでも関連する適用ハンドラでも競合を解消できない場合、その競合はエラー・キューに記録されます。競合が発生した場合は、関連する適用ハンドラを使用してデータベース管理者に通知する必要があります。
競合のためにトランザクションがエラー・キューに移動される場合は、競合の原因となった条件を訂正できる場合があります。このような場合は、DBMS_APPLY_ADMパッケージのEXECUTE_ERRORプロシージャを使用してトランザクションを再実行できます。
|
参照:
|
この項では、使用可能なビルトインの更新の競合ハンドラのタイプと、この種のハンドラでの列リストと解消列の使用方法について説明します。列リストは、更新の競合がある場合に更新の競合ハンドラがコールされる列のリストです。解消列は、更新の競合ハンドラの識別に使用される列です。ビルトインの更新の競合ハンドラMAXIMUMまたはMINIMUMを使用する場合は、解消列も競合の解消に使用されます。解消列は、ハンドラの列リスト内の列の1つである必要があります。
特定の表について1つ以上の更新の競合ハンドラを指定するには、DBMS_APPLY_ADMパッケージのSET_UPDATE_CONFLICT_HANDLERプロシージャを使用します。一意性競合や削除または外部キーの競合には、ビルトインの競合ハンドラはありません。
|
参照:
|
Oracleには、Oracle Streams環境用にOVERWRITE、DISCARD、MAXIMUMおよびMINIMUMタイプのビルトインの更新の競合ハンドラが用意されています。
後述の各タイプのハンドラの説明では、次の競合例を参照しています。
dbs1.netソース・データベースで次の更新が行われます。
UPDATE hr.employees SET salary = 4900 WHERE employee_id = 200; COMMIT;
この更新によって、従業員200の給与が4400から4900に変更されます。
ほぼ同時に、dbs2.net接続先データベースで次の更新が行われます。
UPDATE hr.employees SET salary = 5000 WHERE employee_id = 200; COMMIT;
取得プロセスまたは同期取得は、dbs1.netソース・データベースで更新を取得し、結果の行LCRをキューに入れます。
伝播はdbs1.netのキューからdbs2.netのキューに行LCRを伝播させます。
dbs2.netの適用プロセスは、行LCRをhr.employees表に適用しようとしますが、dbs2.netの給与値は5000であり、これは行LCR内の給与の元の値(4400)と一致しないため、競合が発生します。
ここでは、各ビルトイン競合ハンドラと、それぞれがこの競合をどのように解消するかについて説明します。
競合が発生すると、OVERWRITEハンドラは、接続先データベースの現行の値をソース・データベースからのLCR内の新規の値で置換します。
OVERWRITEハンドラを競合例のdbs2.net接続先データベースのhr.employees表に使用すると、行LCR内の新規の値によってdbs2.netの値が上書きされます。したがって、競合解消後は、従業員200の給与は4900となります。
競合が発生すると、DISCARDハンドラはソース・データベースからのLCR内の値を無視し、接続先データベースの値を保持します。
DISCARDハンドラを競合例のdbs2.net接続先データベースのhr.employees表に使用すると、行LCR内の新規の値が廃棄されます。したがって、競合解消後は、dbs2.netでの従業員200の給与は5000となります。
競合が発生すると、MAXIMUM競合ハンドラは、指定された解消列について、ソース・データベースからのLCRにある新規の値を、接続先データベース内の現行の値と比較します。LCRにある解消列の新規の値が接続先データベースの列の現行値より大きければ、適用プロセスではLCRを使用して競合が解消されます。LCRにある解消列の新規の値が接続先データベースの列の現行値より小さければ、適用プロセスでは接続先データベースを使用して競合が解消されます。
MAXIMUMハンドラを競合例のdbs2.net接続先データベースにあるhr.employees表のsalary列に使用すると、行LCRにある給与は表にある現行の給与より小さいため、適用プロセスでは行LCRは適用されません。したがって、競合解消後は、dbs2.netでの従業員200の給与は5000となります。
関係するトランザクションの時間ベースで競合を解消する場合は、トリガーを使用してトランザクションの時刻が自動的に記録されるように、共有表に列を追加する方法があります。この場合は、この列をMAXIMUM競合ハンドラ用の解消列として指定でき、最新(または最大)時刻を伴うトランザクションが自動的に使用されます。
ここでは、hr.employees表に対するトランザクションの時刻を記録するトリガーの例を示します。job_id、salaryおよびcommission_pct列は、競合解消ハンドラの列リストに含まれているとします。トリガーは、列リスト内の列に対してUPDATEが実行されるか、INSERTが実行された場合にのみ起動します。
ALTER TABLE hr.employees ADD (time TIMESTAMP WITH TIME ZONE);
CREATE OR REPLACE TRIGGER hr.insert_time_employees
BEFORE
INSERT OR UPDATE OF job_id, salary, commission_pct ON hr.employees
FOR EACH ROW
BEGIN
-- Consider time synchronization problems. The previous update to this
-- row might have originated from a site with a clock time ahead of the
-- local clock time.
IF :OLD.TIME IS NULL OR :OLD.TIME < SYSTIMESTAMP THEN
:NEW.TIME := SYSTIMESTAMP;
ELSE
:NEW.TIME := :OLD.TIME + 1 / 86400;
END IF;
END;
/
この種のトリガーを競合解消に使用する場合は、トリガーの起動プロパティがデフォルトの1回起動に設定されていることを確認してください。設定が異なると、適用プロセスによってトランザクションが適用されるときに新規時刻がマークされ、トランザクションの実際時刻が失われる可能性があります。
競合が発生すると、MINIMUM競合ハンドラは、指定された解消列について、ソース・データベースからのLCRにある新規の値を、接続先データベース内の現行の値と比較します。LCRにある解消列の新規の値が接続先データベースの列の現行値より小さければ、適用プロセスではLCRを使用して競合が解消されます。LCRにある解消列の新規の値が接続先データベースの列の現行値より大きければ、適用プロセスでは接続先データベースを使用して競合が解消されます。
MINIMUMハンドラを競合例のdbs2.net接続先データベースにあるhr.employees表のsalary列に使用すると、行LCRにある給与は表にある現行の給与より小さいため、適用プロセスでは行LCRを使用して競合が解消されます。したがって、競合解消後は、従業員200の給与は4900となります。
表に対してビルトインの更新の競合ハンドラを指定するたびに、列リストを指定する必要があります。列リストは、更新の競合ハンドラがコールされる列のリストです。適用プロセスが行LCRを適用するときに、リスト内の1つ以上の列に更新の競合が発生すると、競合を解消するために更新の競合ハンドラがコールされます。リストにない列にのみ競合が発生した場合、更新の競合ハンドラはコールされません。競合解消の有効範囲は、1つの行LCRの1つの列リストです。
特定の表に対して更新の競合ハンドラを複数指定することはできますが、同じ列を複数の列リストに含めることはできません。たとえば、hr.employees表にビルトインの更新の競合ハンドラを2つ指定する場合を考えます。
最初の更新の競合ハンドラの列リストには、列salaryおよびcommission_pctを指定します。
2番目の更新の競合ハンドラの列リストには、列job_idおよびdepartment_idを指定します。
また、この表に他の競合ハンドラは存在しないものと想定します。この場合、適用プロセスが行LCRを適用するときにsalary列に競合が発生すると、それを解消するために最初の更新の競合ハンドラがコールされます。ただし、department_id列に競合が発生すると、それを解消するために2番目の更新の競合ハンドラがコールされます。どの競合ハンドラの列リストにもない列に競合が発生すると、競合ハンドラはコールされず、エラーになります。この例では、hr.employees表のmanager_id列に競合が発生すると、エラーになります。行LCRの適用時に複数の列リストに競合が発生し、列リストにない列での競合がなければ、競合が発生した列リストごとに適切な更新の競合ハンドラが起動されます。
列リストを使用すると、データ型ごとに異なるハンドラを使用して競合を解消できます。たとえば、通常、数値データにはMAXIMUMまたはMINIMUM競合ハンドラが適しており、文字データにはOVERWRITEまたはDISCARD競合ハンドラが適しています。
列リストにない列に競合が発生すると、表に対する特定の操作用のエラー・ハンドラがそれを解消しようとします。エラー・ハンドラが競合を解消できない場合や、この種のエラー・ハンドラがない場合は、競合の原因となったトランザクションがエラー・キューに移動されます。
また、OVERWRITE、MAXIMUMまたはMINIMUMビルトイン・ハンドラのいずれかを使用する列リスト内の列に競合が発生した場合に、この列リスト内のすべての列が行LCRに含まれていないと、使用可能でない値があるため、競合は解消できません。この場合は、競合の原因となったトランザクションがエラー・キューに移動されます。列リストでDISCARDビルトイン方法が使用されている場合は、行LCRにこの列リストのすべての列が含まれていない場合にも、行LCRが廃棄され、エラーは発生しません。
ソース・データベースの複数の列が接続先データベースの列リストに影響する場合は、列リストに指定された列に条件付きのサプリメンタル・ログ・グループを指定する必要があります。サプリメンタル・ロギングをソース・データベースで指定し、競合を正しく解消するために必要な情報をLCRに追加します。通常、列リスト内の列に条件付きのサプリメンタル・ログ・グループを指定する必要があるのは、列リストに列が複数存在する場合であり、列リストに存在する列が1つのみの場合は指定する必要ありません。
ただし、列リストに存在する列が1つのみの場合でも、条件付きのサプリメンタル・ログ・グループが必要になることがあります。つまり、適用ハンドラまたはカスタム・ルールベースの変換で、ソース・データベースからの複数列を接続先データベースの列リストの単一行に結合する場合です。たとえば、カスタム・ルールベースの変換で、通り、州および郵便番号を格納するソース・データベースからの3行を使用し、そのデータを接続先データベースで単一の住所列に結合する場合です。
また、列リストに存在する列が複数の場合でも、条件付きのサプリメンタル・ログ・グループが不要なことがあります。たとえば、適用ハンドラまたはカスタム・ルールベースの変換で、ソース・データベースからの単一の住所列を接続先データベースの列リストの複数行に分割する場合です。カスタム・ルールベースの変換で、ソース・データベースの1つの住所列(通り、州および郵便番号を含む)を取得し、そのデータを接続先データベースの3列に分割する場合です。
|
注意: ビルトインの更新の競合ハンドラでは、LOB、LONG、LONG RAW、ユーザー定義型およびOracleが提供する型の列はサポートされません。したがって、SET_UPDATE_CONFLICT_HANDLERプロシージャを実行するときには、column_listパラメータにこれらの型の列を含めないでください。 |
解消列は、ビルトインの更新の競合ハンドラの識別に使用される列です。ビルトインの更新の競合ハンドラMAXIMUMまたはMINIMUMを使用する場合は、解消列も競合の解消に使用されます。解消列は、ハンドラの列リスト内の列の1つである必要があります。
たとえば、hr.employees表のsalary列をMAXIMUMまたはMINIMUM競合ハンドラの解消列として指定すると、行LCR内の列リストの値を適用するか、接続先データベースにおける列リストの値を保持するかを判断するためにsalary列が評価されます。
競合に解消列が関係する次のどちらの状況でも、エラー・ハンドラで問題を解決できない場合は、適用プロセスは競合の原因となった行LCRを含むトランザクションをエラー・キューに移動します。これらの場合、競合は解消できず、接続先データベースの列の値がそのまま保持されます。
解消列について、新規のLCR値と接続先データベースの行の値が同一の場合(解消列が競合の原因となった列でない場合など)。
解消列の新規のLCR値、または接続先データベースの解消列の現行の値がNULLの場合。
|
注意: 解消列は、OVERWRITEおよびDISCARD競合ハンドラには使用されませんが、これらの競合ハンドラ用に指定する必要があります。 |
複数のデータベース間でデータを共有しており、そのすべてでデータを同一にする必要がある場合は、すべてのデータベースでデータを収束させる競合解消ハンドラを使用してください。すべてのデータベースで共有データの変更を許可する場合、表のデータ収束が可能になるのは、データを共有する全データベースが共有データに対する変更を取得し、データを共有する他のすべてのデータベースにその変更を伝播する場合のみです。
このような環境では、MAXIMUM競合解消方法で収束を保証できるのは、解消列の値が常に増加する場合のみです。時間ベースの解消列は、1行の連続するタイムスタンプが個別であるかぎり、この要件を満たします。このような環境でMINIMUM競合解消方法によって収束を保証できるのは、解消列の値が常に減少する場合のみです。
PL/SQLプロシージャを作成し、カスタム競合ハンドラとして使用できます。特定の表に1つ以上のカスタム競合ハンドラを指定するには、DBMS_APPLY_ADMパッケージのSET_DML_HANDLERプロシージャを使用します。特に、このプロシージャを実行してカスタム競合ハンドラを指定するときに、次のパラメータを設定します。
object_nameパラメータを、競合解消の対象となる表の完全修飾された名前に設定します。
object_typeパラメータをTABLEに設定します。
operation_nameパラメータを、カスタム競合ハンドラのコール対象となる操作のタイプに設定します。可能な操作は、INSERT、UPDATE、DELETEおよびLOB_UPDATEです。 また、デフォルトですべての操作をハンドラのコール対象にする場合は、operation_nameパラメータをDEFAULTに設定します。
エラー・ハンドラでエラー発生時に競合解消を実行する場合は、error_handlerパラメータをTRUEに設定します。また、競合解消をDMLハンドラに組み込む場合は、error_handlerパラメータをFALSEに設定します。
このパラメータをFALSEに設定した場合は、LCR用のEXECUTEメンバー・プロシージャを使用して行LCRを実行すると、指定したオブジェクトと操作に対してDMLハンドラ内で競合解消が実行されます。
user_procedureパラメータを設定して、競合解消用のプロシージャを指定します。このユーザー・プロシージャは、指定したタイプの操作によって指定した表に生じた競合を解消するためにコールされます。
カスタム競合ハンドラが競合を解消できない場合、適用プロセスは、競合を含むトランザクションをエラー・キューに移動し、そのトランザクションを適用しません。
特定のオブジェクトにビルトインの更新の競合ハンドラとカスタム競合ハンドラの両方が存在する場合は、次の両方の条件が満たされる場合にのみビルトインの更新の競合ハンドラが起動されます。
カスタム競合ハンドラで、LCR用のEXECUTEメンバー・プロシージャを使用して行LCRが実行される場合。
行LCR用のEXECUTEメンバー・プロシージャのconflict_resolutionパラメータがTRUEに設定されている場合。
|
参照:
|