部分索引
部分インデックスはそのために最適であり、部分的な複数列インデックスでさえも完璧です。しかし、あなたの状態
1 か月以上前のレコードの値を検索する必要はありません
安定していません。部分インデックスの条件は、リテラルまたはIMMUTABLE
関数、つまり定数値でのみ機能します。あなたは言及Recreate it every month
していますが、それはあなたの定義に同意しませんolder than one month
。違いがわかりますか?
今月 (または先月) だけが必要な場合は、インデックスの再作成とクエリ自体がかなり簡単になります。
この回答の残りの部分では、「1か月以内」というあなたの定義を理解します。以前はこのような状況に対処しなければなりませんでした。次の解決策が私にとって最も効果的でした:
固定のタイムスタンプに基づいてインデックス条件を作成し、クエリで同じタイムスタンプを使用して、部分インデックスを使用できることをクエリ プランナーに納得させます。この種のパーシャルは長期間にわたって有用であり続けますが、新しい行が追加され、古い行が時間枠から脱落するにつれて、その効果が低下するだけです。WHERE
インデックスは、追加の句でクエリから除外する必要がある偽陽性をますます返します。インデックスを再作成して、その状態を更新してください。
テストテーブルを考えると:
CREATE TABLE mytbl (
value text
,creation_date timestamp
);
非常に単純なIMMUTABLE
SQL 関数を作成します。
CREATE OR REPLACE FUNCTION f_mytbl_start_ts()
RETURNS timestamp AS
$func$
SELECT '2013-01-01 0:0'::timestamp
$func$ LANGUAGE sql IMMUTABLE;
部分インデックスの条件で関数を使用します。
CREATE INDEX mytbl_start_ts_idx ON mytbl(value, creation_date)
WHERE (creation_date >= f_mytbl_start_ts());
value
最初に来ます。dba.SE に関するこの関連する回答の説明。
コメントの@Igorからの入力により、回答が改善されました。部分的な複数列インデックスは、部分的なインデックスからの誤検知をより迅速に除外する必要があります。インデックスの状態の性質上、常にますます古くなります (ただし、インデックスがないよりははるかに優れています)。
クエリ
このようなクエリはインデックスを利用し、完全に高速である必要があります。
SELECT value
FROM mytbl
WHERE creation_date >= f_mytbl_start_ts() -- !
AND creation_date >= (now() - interval '1 month')
AND value = 'foo';
冗長に見えるWHERE
句の唯一の目的はcreation_date >= f_mytbl_start_ts()
、クエリ プランナーに部分インデックスを使用させることです。
関数とインデックスを手動で削除して再作成できます。
完全自動化
または、おそらく多くの同様のテーブルを使用して、より大きなスキームで自動化できます。
免責事項:これは高度なものです。自分が何をしているのかを知り、 ユーザー権限、 SQL インジェクションの可能性、同時負荷が大きい場合のロックの問題を考慮する必要があります。
この「ステアリング テーブル」は、レジームのテーブルごとに 1 行を受け取ります。
CREATE TABLE idx_control (
tbl text primary key -- plain, legal table names!
,start_ts timestamp
);
そのようなメタ オブジェクトはすべて別のスキーマに配置します。
この例では:
INSERT INTO idx_control(tbl, value)
VALUES ('mytbl', '2013-1-1 0:0');
「ステアリング テーブル」には、そのようなすべてのテーブルとそれぞれの設定の概要を 1 か所で把握し、それらの一部またはすべてを同期して更新できるという追加の利点があります。
このテーブルを変更するたびにstart_ts
、次のトリガーが開始され、残りの処理が行われます。
トリガー機能:
CREATE OR REPLACE FUNCTION trg_idx_control_upaft()
RETURNS trigger AS
$func$
DECLARE
_idx text := NEW.tbl || 'start_ts_idx';
_func text := 'f_' || NEW.tbl || '_start_ts';
BEGIN
-- Drop old idx
EXECUTE format('DROP INDEX IF EXISTS %I', _idx);
-- Create / change function; Keep placeholder with -infinity for NULL timestamp
EXECUTE format('
CREATE OR REPLACE FUNCTION %I()
RETURNS timestamp AS
$x$
SELECT %L::timestamp
$x$ LANGUAGE SQL IMMUTABLE', _func, COALESCE(NEW.start_ts, '-infinity'));
-- New Index; NULL timestamp removes idx condition:
IF NEW.start_ts IS NULL THEN
EXECUTE format('
CREATE INDEX %I ON %I (value, creation_date)', _idx, NEW.tbl);
ELSE
EXECUTE format('
CREATE INDEX %I ON %I (value, creation_date)
WHERE creation_date >= %I()', _idx, NEW.tbl, _func);
END IF;
RETURN NULL;
END
$func$ LANGUAGE plpgsql;
引き金:
CREATE TRIGGER upaft
AFTER UPDATE ON idx_control
FOR EACH ROW
WHEN (OLD.start_ts IS DISTINCT FROM NEW.start_ts)
EXECUTE PROCEDURE trg_idx_control_upaft();
これで、ステアリング テーブルのシンプルなUPDATE
インデックスと機能が調整されます。
UPDATE idx_control
SET start_ts = '2013-03-22 0:0'
WHERE tbl = 'mytbl';
cron ジョブを実行するか、これを手動で呼び出すことができます。
インデックスを使用するクエリは変更されません。
-> SQLfiddle .
動作することを示すために、10k 行の小さなテスト ケースでフィドルを更新しました。PostgreSQL は、サンプル クエリに対してインデックスのみのスキャンを実行します。これ以上速くなることはありません。