時には、表のトリガーを一時的に使用不可にしたい場合があります。たとえば、日々のSQL操作にトリガーが必要だけれど、特定のスクリプト実行時にはトリガーを作動したくない場合です。標準的な手法では、トリガーをドロップして再度必要になったときに再生成しますが、多くのトリガーを監視しなければならない場合は少し面倒に思うかもしれません。(「あれ、このトリガーのソース・コードはどこに置いたんだっけ?」)
この記事では、トリガーを使用不可にする次の3つの手法を紹介します。
各手法にはそれぞれメリットとデメリットがありますが、それに関してはこの記事の最後に解説します。
この手法は、データベース保守作業の実行とアプリケーションの実行には、通常、異なるユーザーIDが使用されるという事実を利用しています。この手法を実行するには、トリガーを作動したくないときに使用するユーザーIDを選択するだけです。
この手法を実証するためのSQLは、example1.db2スクリプトに含まれています。
セットアップする
この例をセットアップするには、以下の手順に従ってください。
- 2つの表(t1とt2)を作成します。t2への挿入を行うサンプル・トリガーをt1に作成します。
CREATE TABLE db2admin.t1 (c1 int) CREATE TABLE db2admin.t2 (c1 int)
- 次のトリガーを作成します。
CREATE TRIGGER db2admin.trig1 AFTER INSERT ON db2admin.T1 REFERENCING NEW AS o FOR EACH ROW MODE DB2SQL WHEN (USER <> 'ADMINISTRATOR') BEGIN ATOMIC INSERT INTO db2admin.t2 values (o.c1); END
このトリガーは簡単明瞭なものです。USER特別レジスターから戻された接続のユーザーIDが「ADMINISTRATOR」と一致しない場合、t1に挿入された値は必ずt2にも挿入されます。したがって、トリガーを作動したくない場合は、ユーザー「ADMINISTRATOR」として接続し、タスクを実行します。
テストする
- 表t1と表t2およびトリガーtrig1を作成後、「ADMINISTRATOR」以外のユーザーとして接続し、t1に値を挿入します。
INSERT INTO db2admin.t1 VALUES (111)
- トリガーによって、挿入した値が表t2にコピーされていることを確認します。
SELECT * FROM db2admin.t2 C1 ----------- 111 1 record(s) selected.
- 次に、ユーザー「ADMINISTRATOR」として接続し、再度値の挿入を試みます。
INSERT INTO t1 VALUES (222)
- トリガーがアクティブにならなかったため、表t2が変更されていないことを確認します。
SELECT * FROM db2admin.t2 C1 ----------- 111 1 record(s) selected.
ここでは、一時的に使用不可にしなければならないあらゆるトリガーに対して利用可能なトリガー・フレームワークについて説明します。フレームワークを使用するには計画およびトリガー開発者間におけるコンセプトの合意が必要ですが、結果として問題に対する極めて明快な解決策を実現できます。
この手法を実証するためのSQLは、example2.db2スクリプトに含まれています。
動作メカニズムは次のとおりです。
- トリガー名と状態(active = 'Y'または 'N')別にトリガー・リストを保持する、トリガー参照表trigger_stateを定義します。
- トリガーを定義する際、トリガーのWHEN文節内のルックアップをtrigger_state表に追加して、トリガーのアクティブ化の有無を特定します。
セットアップする
この例をセットアップするには、以下の手順に従ってください。
- 2つの表(t1とt2)を作成します。t2への挿入を行うサンプル・トリガーをt1に作成します。
CREATE TABLE db2admin.t1 (c1 int) CREATE TABLE db2admin.t2 (c1 int)
- trigger_state表を作成します。
CREATE TABLE db2admin.trigger_state ( trigschema VARCHAR(128) not null, trigname VARCHAR(30) not null, active char(1) not null )
おそらく最初は、trigschema列とtrigname列を使って主キーをtrigger_state表に置きたいと思うはずです。しかし、最適化に関しては、後述の「パフォーマンスを最適化する」で説明しますので、ここでは、表に何の制約も課さないでおきます。 - トリガーtrig1を表t1に作成するとします。最初にすべきことは、トリガーtrig1をtrigger_state表に登録することです。
INSERT INTO db2admin.trigger_state VALUES ('DB2ADMIN','TRIG1','Y')
ヒント:システム・カタログ表との整合性を保つため、すべての値には大文字を使用します。 - 次に、便宜上、ユーザー定義関数(UDF)を作成します。この目的は、トリガー作成時に明らかになります。
CREATE FUNCTION db2admin.trigger_enabled ( v_schema VARCHAR(128), v_name VARCHAR(30)) RETURNS VARCHAR(1) RETURN (SELECT active FROM db2admin.trigger_state WHERE trigschema=v_schema and trigname=v_name)
重要:この関数は、ルックアップが失敗した場合にヌルを戻します。したがって、trigger_state表を必ず適切に設定して、関数が呼び出されたときに正しいパラメーターを渡すようにします。
ご覧のとおり、この関数はスキーマとトリガー名を入力として受け取り、trigger_state表のルックアップを実行してactive列の値を戻します。 - 次のトリガーを作成します。
CREATE TRIGGER db2admin.trig1 AFTER INSERT ON db2admin.T1 REFERENCING NEW AS o FOR EACH ROW MODE DB2SQL WHEN (db2admin.trigger_enabled('DB2ADMIN','TRIG1') = 'Y') BEGIN ATOMIC INSERT INTO db2admin.t2 values (o.c1); END
このトリガーは簡単明瞭なものです。使用可能にされた場合、t1に挿入された値はすべてt2にも挿入されます。しかしアクティブになる前に、UDF trigger_enabled()を呼び出して、トリガーが使用不可にされているかどうかを確認します。この関数を使用して照会をカプセル化すると、特に多くのトリガーを作成しなければならない場合、エラー発生率が低減します。
ヒント:トリガーが他の条件でWHEN文節をすでに使用している場合、AND演算子を使って条件を一緒にチェーニングします。
テストする
- まず、トリガーが予想どおりに動作するかどうかをテストします。
INSERT INTO db2admin.t1 values (123) DB20000I The SQL command completed successfuly.
- トリガーのアクティブ化により、同様にt2にも値123が挿入されていることを確認します。
SELECT * FROM db2admin.t2 C1 ----------- 123 1 record(s) selected.
- ここで、トリガーを使用不可にします。
UPDATE db2admin.trigger_state SET active='N' WHERE trigschema='DB2ADMIN' and trigname='TRIG1'
- t1に新しい行を挿入します。
INSERT INTO db2admin.t1 values (456)
- 表t2が変更されていないことから、トリガーが使用不可になっていることを確認します。
SELECT * FROM db2admin.t2 C1 ----------- 123 1 record(s) selected.
トリガーを再度使用可能にする
トリガーを再度使用可能にするには、トリガーの状態を再設定するだけです。
UPDATE db2admin.trigger_state SET active='Y'
WHERE trigschema='DB2ADMIN' and trigname='TRIG1'
|
ここまでで、trigger_state表にユニーク制約や索引を作成しませんでした。それは、このトピックに関してはもっと徹底した議論が行われるべきであり、上記手法の実証にはユニーク制約や索引が必要ではなかったからです。
trigger_state表は何百、何千というトリガーを保持する可能性があるため、この表に対して実行されるルックアップのオーバーヘッドを最小化したいと思います。ユニーク索引を作成する主キーをtrigschema列およびtrigname列に作成するよりも、索引ページにactive列を含めるために、別のステップでユニーク索引を作成した方がよいでしょう。余計な入出力を実行して基本表から余計なバイトを取り出すことは、リソースの無駄です。
以下は、キーワードにINCLUDEを使用して、active列のユニーク索引への追加を指定する索引定義です。
CREATE UNIQUE INDEX db2admin.trigstateIX
ON db2admin.trigger_state (trigschema, trigname)
INCLUDE (active)
|
trigger_state表で数千個のトリガーを保持する場合、この表を独自の表スペースに配置して専用のバッファー・プールを割り当てた方がよいかもしれません。そうすれば、参照表を常にメモリー内に保持できます。trigger_state表のすべての行が確実にメモリー内に保持されるようにバッファー・プールのサイズを調整しますが、メモリーの無駄になるためサイズをあまり大きくし過ぎないようにしてください。(コマンドLIST TABLESPACES SHOW DETAILの出力を使用して、バッファー・プールのサイズを調整できます。)トリガーが数千個に満たない場合、この最適化を実行する価値はないでしょう。なぜなら、trigger_stateの行サイズは約41バイト(trigschema列に20バイト、trigname列に20バイト、状態列に1バイトを想定)しかないため、4キロバイトのページにそれぞれ100トリガー分の情報を格納できるからです。
数千個のトリガーを保持する場合、trigger_state表に対して統計を実行するのを忘れないでください。
もう1つの考慮すべき大事なことは、当然のことですが、周期的に使用不可にする必要があるトリガーに対してのみこの手法を適用することです。
手法3:SQLストアード・プロシージャーを使ってトリガーを保持する
手法1および手法2では、トリガーのドロップや再生成による影響を心配せずにすむように、トリガーを使用不可にする方法について説明しました。このセクションではSQLストアード・プロシージャーを使用して、トリガーのドロップや再生成をカプセル化して管理する解決策を紹介します。そのメカニズムは、ソース・コードがデータベースから絶対に離れないように設計されているため、トリガーのソース・コードをトラッキングする必要はありません。
動作メカニズムは次のとおりです。
- 次の3つのストアード・プロシージャーを作成します。
- disable_trigger() - トリガーを使用不可にします
- enable_trigger() - トリガーを使用可能にします
- show_disabled_triggers() - すべての使用不可トリガーを表示します
- trigtool.disabled_triggersという表を作成します。これは、syscat.triggersシステム・カタログ表と似ています。ドロップしたトリガーのコピーを保持しますが、最初は空です。
- トリガーを使用不可にしなければならない場合、disable_trigger()を呼び出します。このコマンドは、トリガー定義をsyscat.triggersからdisabled_triggers表にコピーし、トリガーをドロップします。
- トリガーを使用可能にするには、enable_trigger()を呼び出します。このコマンドは、trigtool.disabled_triggers表からトリガーを再生成します。]
制約事項:この手法では、コード・テキストが約30KBを超えるトリガーを使用不可にできません。
ストアード・プロシージャーを使用すると、実際のトリガーのドロップや再生成よりも抽象化レベルでトリガーを使用不可/使用可能にできます。ここでは、上記ストアード・プロシージャーを実装するためのすべてのソース・コードを紹介しています。ただし、コードを使用する前に必ず後述の「特記事項」をお読みください。
セットアップする すべてのオブジェクトはtrigtoolスキーマを使用して作成され、すべてのDDLはスクリプトexample3.db2に含まれています。
- 32KBページ・サイズのバッファー・プールと32KBページ・サイズの表スペースを作成します。
CREATE BUFFERPOOL BP32K SIZE 1000 PAGESIZE 32K CREATE TABLESPACE TS32K PAGESIZE 32K MANAGED BY SYSTEM USING ('c:\ts32k\') BUFFERPOOL BP32K
- trigtool.disabled_triggers表を作成します。
CREATE TABLE TRIGTOOL.DISABLED_TRIGGERS ( TRIGSCHEMA VARCHAR(128) not null, TRIGNAME VARCHAR(128) not null, TABSCHEMA VARCHAR(128) not null, TABNAME VARCHAR(128) not null, QUALIFIER VARCHAR(128) not null, FUNC_PATH VARCHAR(254) not null, TEXT VARCHAR(31500) not null ) in TS32K ALTER TABLE TRIGTOOL.DISABLED_TRIGGERS ADD CONSTRAINT disabledtrig_pk PRIMARY KEY (trigschema, trigname)
trigtool.disabled_triggers表の重要な特徴は次のとおりです。- まったく同じではありませんが、syscat.triggersとよく似ています。トリガーの再生成に必要な列のみが含まれています。
- 表スペースTS32K(ページ・サイズが32KBの表スペース)内に作成されています。
- 主キー制約は、トリガー・スキーマおよびトリガー名に対して加えられています。
- TEXT列のタイプはVARCHAR(31500)です。これは、CLOBタイプを使うsyscat.triggersのTEXT列とは異なります。VARCHARタイプが必要な理由は、後で説明します。
- 現在の使用不可トリガーの表示メソッドを提供するプロシージャーtrigtool.show_disabled_triggers()を作成します。これは基本的に、プロシージャー呼び出し側のカーソルとしてスキーマおよび使用不可のカーソル名を戻し、アプリケーションまたはユーザーはコマンド行プロセッサー(CLP)からリトリーブできます。このプロシージャーのソース・コードは、次のとおりです。
CREATE PROCEDURE TRIGTOOL.SHOW_DISABLED_TRIGGERS () LANGUAGE SQL RESULT SETS 1 BEGIN DECLARE c_triggers CURSOR WITH RETURN FOR SELECT trigschema, trigname FROM TRIGTOOL.DISABLED_TRIGGERS; OPEN c_triggers; END
- これで、使用不可トリガーを表示するメソッドを作成できたので、trigtool.disable_trigger()という実際にトリガーをコピーしてドロップするプロシージャーを作成できます。
CREATE PROCEDURE TRIGTOOL.DISABLE_TRIGGER ( IN v_schema VARCHAR(128), IN v_name VARCHAR(128)) SPECIFIC DISABLE_TRIGGER LANGUAGE SQL BEGIN DECLARE SQLCODE INT DEFAULT 0; DECLARE v_stmt VARCHAR(250); DECLARE EXIT HANDLER FOR NOT FOUND SIGNAL SQLSTATE '80000' SET MESSAGE_TEXT='Trigger Not Found'; DECLARE EXIT HANDLER FOR SQLWARNING SIGNAL SQLSTATE '80001' SET MESSAGE_TEXT='Unable to disable trigger'; INSERT INTO TRIGTOOL.DISABLED_TRIGGERS SELECT TRIGSCHEMA, TRIGNAME, TABSCHEMA, TABNAME, QUALIFIER, FUNC_PATH, CAST(TEXT as VARCHAR(31500)) FROM SYSCAT.TRIGGERS WHERE TRIGSCHEMA = v_schema and TRIGNAME = v_name AND VALID='Y'; SET v_stmt = 'DROP TRIGGER ' || v_schema || '.' ||v_name; EXECUTE IMMEDIATE v_stmt; END
このプロシージャーは、2つのパラメーター(スキーマと使用不可にするトリガー名)を取ります。
最初のオペレーションはINSERTです。これは、syscat.triggers表からtrigtool.disabled_triggers表に情報をコピーします。syscat.triggersのTEXT列は、CLOBデータ・タイプからVARCHAR(31500)データ・タイプにキャストされていることに注意してください。コピーされると、トリガーは動的SQLによってドロップされます。例外ハンドラーが定義されていないため、エラーが生じるとロールバックが起こり、オペレーションはリジェクトされます。安全のため、SQLWARNINGに対してハンドラーが宣言されています。このハンドラーは、SQLEXCEPTIONをシグナル通知し、ロールバックを引き起こします。すなわち、トリガーがドロップされるのは、オペレーションのシーケンス全体をエラーや警告なしで終了できた場合だけです。
- trigtool.enable_trigger()を作成します。これは、trigtool.disabled_triggers表からトリガーを再生成します。
CREATE PROCEDURE TRIGTOOL.ENABLE_TRIGGER ( IN v_schema VARCHAR(128), IN v_name VARCHAR(128)) LANGUAGE SQL BEGIN DECLARE SQLCODE INT DEFAULT 0; DECLARE v_qualifier VARCHAR(128); DECLARE v_func_path VARCHAR(1000); DECLARE v_stmt VARCHAR(32672); DECLARE v_curr_qualifier VARCHAR(128); DECLARE v_curr_funcpath VARCHAR(1000); DECLARE EXIT HANDLER FOR SQLWARNING SIGNAL SQLSTATE '80000' SET MESSAGE_TEXT = 'Error. Manual recreation required'; SET v_curr_qualifier = CURRENT SCHEMA; SET v_curr_funcpath = CURRENT FUNCTION PATH; SELECT qualifier, func_path, TEXT into v_qualifier, v_func_path, v_stmt FROM TRIGTOOL.DISABLED_TRIGGERS WHERE trigschema=v_schema and trigname=v_name; SET v_func_path = 'SET CURRENT FUNCTION PATH = ' || v_func_path; EXECUTE IMMEDIATE v_func_path; SET v_qualifier = 'SET CURRENT SCHEMA = ' || v_qualifier; EXECUTE IMMEDIATE v_qualifier; EXECUTE IMMEDIATE v_stmt; DELETE FROM TRIGTOOL.DISABLED_TRIGGERS WHERE trigschema=v_schema and trigname=v_name; SET v_curr_qualifier = 'SET SCHEMA = ' || v_curr_qualifier; SET v_curr_funcpath = 'SET CURRENT FUNCTION PATH = ' || v_curr_funcpath; EXECUTE IMMEDIATE v_curr_qualifier; EXECUTE IMMEDIATE v_curr_funcpath; END
このプロシージャーは、trigtool.disable_trigger()と同じように2つのパラメーター(スキーマと使用可能にするトリガー名)を受け取ります。まず、現行セッションの現行スキーマおよび現行関数パスを格納して、プロシージャーの実行終了後に復元できるようにします。次に、qualifier、func_path、およびtextを、trigtool.disabled_triggers表からリトリーブします。
qualifierは、トリガーを最初に作成したときに非修飾表および非修飾ビューの修飾に使ったスキーマを含んでいます。同様に、func_path値は、トリガーを最初に作成したときに使った関数パスを表します。関数パスは、トリガー定義に存在する可能性のある非修飾関数を解決するのに使用します。text列は、トリガー作成に使用したオリジナル・テキストを含んでいます。
トリガーを復元する前に、関数パスおよび現行スキーマの値を設定して、トリガー・テキスト実行時にすべての非修飾オブジェクト参照に適切な修飾子と関数パスが使用できるようにします。次に、textを使ってトリガーを再生成し、trigtool.disabled_triggersからトリガーのコピーを削除します。コードを見ると、トリガー・テキストが30KBに制限されている理由がわかります。EXECUTE IMMEDIATEは、パラメーターにCLOBタイプをサポートしていません。これが、syscat.triggersのオリジナル・テキストをCLOBからVARCHARにキャストしなければならなかった理由です。
最後に、現行スキーマと現行関数パスを、それぞれ前の値に復元します。
trigtool.disable_trigger()と同様に、オペレーションのシーケンス全体でエラーや警告を生じないようにしなければなりません。エラーまたは警告が生じると、オペレーション全体がロールバックされます。
テストする
すべてが正しく作成された後、トリガーの使用可/使用不可をテストできます。このテストは、スクリプトexample4.db2に含まれています。この例をセットアップするには、以下の手順に従ってください。
- 次の2つの表(t1とt2)を作成します。
CREATE TABLE db2admin.t1 (c1 int) CREATE TABLE db2admin.t2 (c1 int)
- トリガーが予想どおりに動作するかどうかテストします。
INSERT INTO db2admin.t1 values (123) DB20000I The SQL command completed successfully.
- トリガーのアクティブ化により、同様にt2にも値123が挿入されていることを確認します。
SELECT * FROM db2admin.t2 C1 ----------- 123 1 record(s) selected.
- ここで、トリガーを使用不可にします。
CALL trigtool.disable_trigger('DB2ADMIN','TRIG1')
- trigtool.show_disabled_triggers()を呼び出すことによって、トリガーが使用不可になっていることを確認できます。
CALL trigtool.show_disabled_triggers() Result set 1 -------------- TRIGSCHEMA TRIGNAME --------------------------- -------------------- DB2ADMIN TRIG1 1 record(s) selected. Return Status = 0)
- t1に新しい行を挿入します。
INSERT INTO db2admin.t1 values (456) SELECT * FROM db2admin.t2 C1 ----------- 123 1 record(s) selected.
予想どおり、トリガーが使用不可にされているため、表t2は変更されていません。
トリガーを再度使用可能にする
トリガーを再度使用可能にするには、次のようにスキーマとトリガー名を持ったenable_trigger()ストアード・プロシージャーを呼び出すだけです。
CALL trigtool.enable_trigger('DB2ADMIN','TRIG1')
|
説明を簡単にするために、このサンプル・テストではストアード・プロシージャーの正確さを完全には示していません。しかしこれは、デフォルト以外の(ランダムに選択されたCURRENT SCHEMAとCURRENT FUNCTION PATH特別レジスターによって修飾された)スキーマと関数パスを使用してトリガーを作成したときに動作することがテストされています。複雑な場合のテスト・ケースは、example5.db2スクリプトに含まれています。複雑なケースの解析は、読者の演習として残しておきます。
この記事では、トリガーを使用不可/使用可能にする3つの手法を紹介しました。その手法とは、ユーザー、参照表、およびストアード・プロシージャーを使ったトリガーのドロップと再生成の管理によるものです。各手法にはメリットとデメリットがあり、また手法の優劣を判断する要件は環境ごとに異なります。
各手法のメリットとデメリットを表1に示します。
フィードバックをお待ちしています。連絡先は、Paul Yip(ypaul@ca.ibm.com)です。
表1. トリガーを使用不可/使用可能にする3つの手法の比較
| 手法 | メリット | デメリット |
|---|---|---|
| ユーザーに対する 使用不可 |
|
|
| トリガー・フレーム ワーク |
|
|
| ストアード・プロ シージャー |
|
|
極めて貴重なレビューをしていただいたSerge Riealu氏に対し、謝意を表します。Riealu氏の提案のおかげで、はるかによい記事になりました。
本記事には、サンプル・コードが含まれています。IBMは、読者(「ライセンス所有者」)に、このサンプル・コードを使用する非排他的でロイヤルティーのないライセンスを付与するものとします。ただし、このサンプル・コードは現状のまま提供され、黙示の商品性の保証、特定目的への適合性の保証、および権利の不侵害の保証を含む、明示または黙示の保証なく提供されるものです。IBMとIBMのライセンス交付者は、ソフトウェアの使用の結果としてライセンス所有者が受けた損害については、一切の責任を負いません。いかなる場合においても、このソフトウェアの使用または使用不能の結果として生ずる逸失売上げ、逸失利益、データの損失、直接的損害、間接的損害、特別の損害、結果的損害、付随的損害、あるいは懲罰的損害賠償について、原因の如何また責任論の種類の如何にかかわらず、たとえこの種の損害発生の可能性があることをIBMがあらかじめ警告されている場合であっても、IBMとIBMのライセンス交付者は一切責任を負わないものとします。
以下のリンクをクリックして、ダウンロードするファイル・リストを確認してください。ファイルをダウンロードする前に、使用許諾契約書に同意する必要があります。
-
developerWorks Japan: Information Management : Information Managementの日本語技術情報サイトです
-
developerWorks: Information Management(US) : Information Managementの英語の技術情報サイトです
Erasmo Acostaは、データベース業界で合計15年間に及ぶ経験を有しています。ソフトウェア開発から高度なテクニカル・サポートやミッション・クリティカルなテクニカル・サポートのコンサルタントに至るまで、さまざまな分野で仕事をしてきました。Acosta氏は、InformixRの買収によりIBMに入社しました。ITスペシャリストとして、他のデータベースからDB2への移行、およびIBM データ管理テクノロジーが持つ多くのメリットの理解と利用について顧客をサポートしています。
Tony Leeは、DB2ファミリー製品について長い間取り組んできました。長年の間、マネージャー、プランナー、および開発者として、IMSの管理からDataJoinerの2フェーズ・コミットの遂行にまで及ぶ、IBM Data Managementファミリーの幅広い製品群にかかわる仕事をしてきました。現在Lee氏は、Data Management Business Partners Technical Enablementチームのシニア・コンサルタントとして、IBMビジネス・パートナーに対して分散プラットフォーム用DB2に関するコンサルテーションやサポートを提供しています。専門は、データのフェデレーションとレプリケーションです。