PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Datetime-Einschränkung zu einem mehrspaltigen PostgreSQL-Partialindex hinzufügen

Sie erhalten eine Ausnahme mit now() weil die Funktion nicht IMMUTABLE ist (offensichtlich) und unter Angabe von das Handbuch :

Ich sehe zwei Möglichkeiten, einen (viel effizienteren) Teilindex zu verwenden:

1. Teilindex mit Bedingung unter Verwendung von Konstante Datum:

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

Angenommen created ist tatsächlich als timestamp definiert . Es würde nicht funktionieren, einen timestamp anzugeben Konstante für einen timestamptz Spalte (timestamp with time zone ). Die Umwandlung von timestamp zu timestamptz (oder umgekehrt) hängt von der aktuellen Zeitzoneneinstellung ab und ist nicht unveränderlich . Verwenden Sie eine Konstante mit passendem Datentyp. Verstehen Sie die Grundlagen von Zeitstempeln mit / ohne Zeitzone:

Löschen und neu erstellen diesen Index zu Stunden mit wenig Verkehr, vielleicht mit einem Cron-Job auf täglicher oder wöchentlicher Basis (oder was auch immer gut genug für Sie ist). Das Erstellen eines Indexes ist ziemlich schnell, insbesondere eines vergleichsweise kleinen Teilindexes. Auch diese Lösung muss der Tabelle nichts hinzufügen.

Vorausgesetzt kein gleichzeitiger Zugriff für die Tabelle könnte eine automatische Indexerneuerung mit einer Funktion wie dieser durchgeführt werden:

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$;

Aufruf:

SELECT f_index_recreate();

now() (wie Sie es hatten) entspricht CURRENT_TIMESTAMP und gibt timestamptz zurück . In timestamp umwandeln mit now()::timestamp oder verwenden Sie LOCALTIMESTAMP stattdessen.

db<>fiddle hier
Altes sqlfiddle

Wenn Sie sich mit gleichzeitigem Zugriff befassen müssen zur Tabelle, verwenden Sie DROP INDEX CONCURRENTLY und CREATE INDEX CONCURRENTLY . Aber Sie können diese Befehle nicht in eine Funktion packen, weil pro Dokumentation :

Also mit zwei getrennten Transaktionen :

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

Dann:

DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;

Optional in alten Namen umbenennen:

ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;

2. Teilindex mit Bedingung auf "archiviert"-Tag

Fügen Sie einen archived hinzu Tag zu Ihrer Tabelle hinzufügen:

ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;

UPDATE die Spalte in Intervallen Ihrer Wahl, um ältere Zeilen "zurückzuziehen" und einen Index wie diesen zu erstellen:

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

Fügen Sie Ihren Abfragen eine Übereinstimmungsbedingung hinzu (auch wenn sie redundant erscheint), damit sie den Index verwenden kann. Überprüfen Sie dies mit EXPLAIN ANALYZE ob sich der Abfrageplaner durchsetzt - er sollte in der Lage sein, den Index für Abfragen zu einem neueren Datum zu verwenden. Aber komplexere Bedingungen, die nicht genau übereinstimmen, werden nicht verstanden.

Sie müssen nicht den Index löschen und neu erstellen, sondern das UPDATE auf dem Tisch kann teurer sein als die Neuerstellung des Index und der Tisch wird etwas größer.

Ich würde mit dem ersten gehen Option (Neuerstellung des Index). Tatsächlich verwende ich diese Lösung in mehreren Datenbanken. Die zweite verursacht kostspieligere Updates.

Beide Lösungen behalten ihre Nützlichkeit im Laufe der Zeit bei, die Leistung nimmt langsam ab, wenn mehr veraltete Zeilen in den Index aufgenommen werden.