5

次の列を含むテーブルがあります。

  • という名前の整数列id
  • という名前のテキスト列value
  • という名前のタイムスタンプ列creation_date

id現在、および列の索引が作成されていvalueます。

このテーブルで特定の値を検索する必要があり、できるだけ速く検索したいと考えています。しかし、1 か月以上前のレコードに目を通す必要はありません。したがって、理想的には、それらをインデックスから除外したいと考えています。

これを達成するための最良の方法は何でしょう:

  1. テーブルのパーティショニングを実行します。該当する月のサブテーブルのみを検索します。
  2. 最近のレコードのみを含む部分インデックスを作成します。毎月作り直してください。
  3. 他の何か?

(追伸: 「最適なソリューション」とは、最も便利で、高速で、保守が容易なソリューションを意味します)

4

1 に答える 1

4

部分索引

部分インデックスはそのために最適であり、部分的な複数列インデックスでさえも完璧です。しかし、あなたの状態

1 か月以上前のレコードの値を検索する必要はありません

安定していません。部分インデックスの条件は、リテラルまたはIMMUTABLE関数、つまり定数値でのみ機能します。あなたは言及Recreate it every monthしていますが、それはあなたの定義に同意しませんolder than one month。違いがわかりますか?

今月 (または先月) だけが必要な場合は、インデックスの再作成とクエリ自体がかなり簡単になります。

この回答の残りの部分では、「1か月以内」というあなたの定義を理解します。以前はこのような状況に対処しなければなりませんでした。次の解決策が私にとって最も効果的でした:

固定のタイムスタンプに基づいてインデックス条件を作成し、クエリで同じタイムスタンプを使用して、部分インデックスを使用できることをクエリ プランナーに納得させます。この種のパーシャルは長期間にわたって有用であり続けますが、新しい行が追加され、古い行が時間枠から脱落するにつれて、その効果が低下するだけです。WHEREインデックスは、追加の句でクエリから除外する必要がある偽陽性をますます返します。インデックスを再作成して、その状態を更新してください。

テストテーブルを考えると:

CREATE TABLE mytbl (
   value text
  ,creation_date timestamp
);

非常に単純なIMMUTABLESQL 関数を作成します。

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 は、サンプル クエリに対してインデックスのみのスキャンを実行します。これ以上速くなることはありません。

于 2013-04-23T15:41:20.513 に答える