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.