13.7. ルール対トリガ

トリガによって行われる多くの操作はPostgreSQLのルールシステムで実装可能です。現在実際にルールで実装できないものはある種の制約に関してです。 もし他のテーブルに列の値がなかった場合、条件ルールで問い合わせをNOTHINGに書き換えてしまうことも可能ですが、これではデータがだまって消去されてしまい、よいアイディアとはいえません。有効な値かどうかのチェックが必要で、無効な値についてはエラーメッセージを表示する必要があるなら、このことは今のところトリガを使って行わなければなりません。

一方、ビュー上でINSERTによって起動されたトリガは、データをどこかに退避させ、ビューへの挿入を禁止するルールと同じことができます。しかしUPDATEまたはDELETEではスキャンされる実データがビューリレーションに存在しないのでトリガが呼ばれることがない点で相違しています。ルールで解決するしかありません。

いずれによっても実装されるこれらの機能に関してどちらがベストかはデータベースの使用法によります。トリガはどの行にたいしても一度だけ起動します。ルールはパースツリーを操作するか追加のパースツリーを生成します。ですから、1つの命令文が多くの行に影響を与える場合、1つの行を処理するたびに呼び出され、その実行を何回も行わなければならないトリガよりも、1つの別の問い合わせを発行するルールのほうが通常よい働きをします。

たとえば、2つのテーブルがあるとします。

CREATE TABLE computer (
    hostname        text,    -- indexed
    manufacturer    text     -- indexed
);

CREATE TABLE software (
    software        text,    -- indexed
    hostname        text     -- indexed
);

2つのテーブルには共に数千の行があって、hostname上の索引は一意です。hostnameの列にはコンピュータのFQDN(絶対ドメイン名)があります。ルール/トリガは削除されたホストを参照する、softwareにおける行の削除を制限しなければなりません。 トリガは、computer から削除されたそれぞれの独立した行で呼び出されるため、以下のような命令文を、準備され保存された計画の中で使用し、パラメータ中に hostname を引き渡すことができます。

DELETE FROM software WHERE hostname = $1;

このときのルールは、以下のように書き表すことができます。

CREATE RULE computer_del AS ON DELETE TO computer
    DO DELETE FROM software WHERE hostname = OLD.hostname;

ここで、異なった種類の削除について見てみましょう。

DELETE FROM computer WHERE hostname = 'mypc.local.net';

この場合、テーブル computer はインデックスでスキャンされ (高速です)、トリガによって発行された問い合わせも同様インデックススキャンとなります (これも高速です)。ルールからの特別の問い合わせは以下のようになります。

DELETE FROM software WHERE computer.hostname = 'mypc.local.net'
                       AND software.hostname = computer.hostname;

そこには適切なインデックスが用意されているので、プランナは以下の計画を生成します。

ネストループ
  ->  computer 上で comp_hostidx を使ったインデックススキャン
  ->  software に対して soft_hostidx を使用したインデックススキャン

トリガとルール実装の間で処理速度の差はそれほどはないでしょう。次の削除処理ではhostnameが「old」で始まる2,000台すべての computerを削除しようと思います。 方法として2つの有効な問い合わせがあって、1つは 以下のようなものです。

DELETE FROM computer WHERE hostname >= 'old'
                       AND hostname <  'ole'

ここで問い合わせのルールに対する計画は以下のようなものです。

ハッシュ結合
  ->  software 上で順スキャン
  ->  ハッシュ
    ->  computer 上で comp_hostidx を使ったインデックススキャン

もう1つの可能な問い合わせは以下のようなものです。

DELETE FROM computer WHERE hostname ~ '^old';

これは以下の実行計画を持っています。

ネストループ
  ->  computer 上で comp_hostidx を使ったインデックススキャン
  ->  software に対して soft_hostidx を使用したインデックススキャン

これが示していることは、AND で結合された複数の検索条件が存在する場合、プランナは regexp バージョンの問い合わせでは行っているのに、computer 上の hostname に対する検索条件を software 上のインデックススキャンにも同様に使用できることを理解していないということです。トリガは削除されるべき2,000台の旧式コンピュータのいずれかによって1回呼び出され、結果computer上で1回のインデックススキャンとsoftwareに対して2,000回のインデックススキャンが行われます。ルールの実装ではインデックス上で2つの問い合わせによって実行されます。逐次スキャンの場合でもルールがより速いかどうかはsoftwareテーブルの大きさに依存します。参照するすべてのインデックスブロックが間もなくキャッシュに現れるとしても、SPIマネージャに対する2,000回の問い合わせの実行には時間を要します。

最後の問い合わせを見てみましょう。

DELETE FROM computer WHERE manufacurer = 'bim';

この文でもcomputerからたくさんの行が削除される結果となります。ここでもトリガはエクゼキュータに多くの問い合わせを発行することになります。ルール計画もまた前回同様2つのインデックススキャンのネストループとなります。computerの別のインデックスを使えば、以下のようになります。

ネストループ
  ->  computer に対して comp_manufidx を使用したインデックススキャン
  ->  software に対して soft_hostidx を使用したインデックススキャン

これらのルール問い合わせの結果は、以下のようなものです。

DELETE FROM software WHERE computer.manufacurer = 'bim'
                       AND software.hostname = computer.hostname;

いずれの場合においても、ルールシステムが生成する余分な問い合わせは影響を受ける行数からは多かれ少なかれ独立しています。

別の状況として、アクションを実行すべきか否かが変更された属性に依存する、UPDATEの場合があります。PostgreSQL 6.4では、ルールイベントにおける属性指定はできなくなっています(遅くとも6.5あるいはもう少し早くに使えるようになるでしょう。 しばらくお待ち下さい)。したがって、現時点でshoelace_logの例のようなルールを作成する唯一の方法は、ルール条件を使用することです。初めの問い合わせの目的リストに出てこないため、これは対象とする属性が変更されなくても常に実行される特別な問い合わせとなります。 再度使えるようになった場合は、トリガに対するルールのもう 1 つの強みになります。この場合、トリガの最適化はその定義により不可能です。 特定の属性が変更されたときにのみそのアクションを行うという処理自体はその(トリガ)関数内部に隠されているからです。トリガの定義は行レベルのみに対して認められるので、行が触られるとトリガが決定が下せるようにトリガが呼び出されなければなりません。ルールシステムの場合は対象リストを検索することで変更されたかどうかわかりますので、属性が触られていなければその後の問い合わせをすべて中止することができます。条件の有無にかかわらず、ルールは実行しなければならないことがあればスキャンを実行します。

問い合わせ結果が大きく、プランナがうまく結合条件を設定できないような状況下でのみルールはトリガに比べて明らかに遅くなります。 ルールは大きな金槌のようなもので、注意を怠ると大怪我をしますがうまく使えばどんな釘でもうまく打ち込めるのです。