8

queries_query多くの列を持つという名前の PostgreSQL テーブルがあります。

これらの列のうちの 2 つ と はcreateduser_sid特定のユーザーが過去 30 日間に実行したクエリの数を判断するために、アプリケーションによって SQL クエリで頻繁に使用されます。直近の 30 日間よりも古い時間についてこれらの統計を照会することは非常にまれです。

これが私の質問です:

現在、次を実行して、これらの 2 つの列に複数列のインデックスを作成しました。

CREATE INDEX CONCURRENTLY some_index_name ON queries_query (user_sid, created)

しかし、インデックスをさらに制限して、作成日が過去 30 日以内にあるクエリのみを対象にしたいと考えています。私は次のことを試しました:

CREATE INDEX CONCURRENTLY some_index_name ON queries_query (user_sid, created)
WHERE created >= NOW() - '30 days'::INTERVAL`

しかし、これは私の関数が不変でなければならないという例外をスローします。

インデックスを最適化し、Postgres がこれらの繰り返しクエリを実行するために必要なリソースを削減できるように、これを機能させたいと思っています。

4

1 に答える 1

14

now()関数が(明らかに)そうではないため、を使用すると例外が発生しマニュアルIMMUTABLEを引用します。

インデックス定義で使用されるすべての関数と演算子は「不変」である必要があります...

(はるかに効率的な)部分インデックスを利用する2つの方法があります。

1.定数日付を使用した条件付きの部分インデックス:

CREATE INDEX queries_recent_idx ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp;

仮定 createdは実際にはとして定義されtimestampます。列( )timestampに定数を指定することはできません。からへのキャスト(またはその逆)は、現在のタイムゾーン設定に依存し、不変ではありません。一致するデータ型の定数を使用します。タイムゾーンがある場合とない場合のタイムスタンプの基本を理解します。timestamptztimestamp with time zonetimestamptimestamptz

トラフィックの少ない時間にそのインデックスを削除して再作成します。おそらく、毎日または毎週(またはあなたにとって十分なものなら何でも)cronジョブを使用します。インデックスの作成は非常に高速で、特に部分インデックスは比較的小さいです。このソリューションでは、テーブルに何も追加する必要もありません。

テーブルへの同時アクセスがないと仮定すると、次のような関数を使用してインデックスの自動再作成を実行できます。

CREATE OR REPLACE FUNCTION f_index_recreate()
  RETURNS void
  LANGUAGE plpgsql AS
$func$
BEGIN
   DROP INDEX IF EXISTS queries_recent_idx;
   EXECUTE format('
      CREATE INDEX queries_recent_idx
      ON queries_query (user_sid, created)
      WHERE created > %L::timestamp'
    , LOCALTIMESTAMP - interval '30 days');  -- timestamp constant
--  , now() - interval '30 days');           -- alternative for timestamptz
END
$func$;

電話:

SELECT f_index_recreate();

now()(あなたが持っていたように)はと同等でCURRENT_TIMESTAMPあり、を返しますtimestamptz。にキャストするtimestampnow()::timestampLOCALTIMESTAMP代わりに使用してください。

db <> fiddle here
Old sqlfiddle


テーブルへの同時アクセスを処理する必要がある場合は、とを使用DROP INDEX CONCURRENTLYCREATE INDEX CONCURRENTLYます。ただし、ドキュメントによると、これらのコマンドを関数にラップすることはできません。

...通常のCREATE INDEXコマンドはトランザクションブロック内で実行できますが、実行CREATE INDEX CONCURRENTLYできません。

したがって、2つの別々のトランザクションで:

CREATE INDEX CONCURRENTLY queries_recent_idx2 ON queries_query (user_sid, created)
WHERE  created > '2013-01-07 00:00'::timestamp;  -- your new condition

それで:

DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;

必要に応じて、古い名前に名前を変更します。

ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;

2.「アーカイブ済み」タグを条件とする部分インデックス

テーブルにタグを追加archivedします。

ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;

UPDATE古い行を「廃止」し、次のようなインデックスを作成するために選択した間隔で列を作成します。

CREATE INDEX some_index_name ON queries_query (user_sid, created)
WHERE NOT archived;

クエリに一致条件を追加して(冗長に見える場合でも)、インデックスを使用できるようにします。クエリプランナーがキャッチするかどうかを確認しEXPLAIN ANALYZEます-新しい日付のクエリにインデックスを使用できるはずです。しかし、正確に一致しないより複雑な条件は理解できません。

インデックスを削除して再作成する必要はありませんがUPDATE、テーブル上のはインデックスの再作成よりもコストがかかる可能性があり、テーブルは少し大きくなります。

私は最初のオプション(インデックスのレクリエーション)を選びます。実際、私はこのソリューションをいくつかのデータベースで使用しています。2つ目は、よりコストのかかる更新が発生します。

どちらのソリューションも時間の経過とともにその有用性を維持し、より古い行がインデックスに含まれるため、パフォーマンスは徐々に低下します。

于 2013-02-07T08:13:28.313 に答える