Du får en undtagelse ved at bruge now()
fordi funktionen ikke er IMMUTABLE
(naturligvis) og med henvisning til manualen
:
Jeg ser to måder at bruge et (meget mere effektivt) delvist indeks på:
1. Delvis indeks med betingelse ved hjælp af konstant dato:
CREATE INDEX queries_recent_idx ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp;
Forudsat created
er faktisk defineret som timestamp
. Det ville ikke fungere at angive et timestamp
konstant for en timestamptz
kolonne (timestamp with time zone
). Castet fra timestamp
til timestamptz
(eller omvendt) afhænger af den aktuelle tidszoneindstilling og er ikke uforanderlig . Brug en konstant for matchende datatype. Forstå det grundlæggende i tidsstempler med/uden tidszone:
Slip og genskab det indeks på timer med lav trafik, måske med et cron-job på daglig eller ugentlig basis (eller hvad der nu er godt nok for dig). Oprettelse af et indeks er ret hurtigt, især et delvist indeks, der er forholdsvis lille. Denne løsning behøver heller ikke at tilføje noget til tabellen.
Forudsat ingen samtidig adgang til tabellen, kan automatisk genskabelse af indeks udføres med en funktion som denne:
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$;
Ring til:
SELECT f_index_recreate();
now()
(som du havde) svarer til CURRENT_TIMESTAMP
og returnerer timestamptz
. Cast til timestamp
med now()::timestamp
eller brug LOCALTIMESTAMP
i stedet.
db<>fiddle her
Gamle sqlfiddle
Hvis du skal håndtere samtidig adgang til tabellen skal du bruge DROP INDEX CONCURRENTLY
og CREATE INDEX CONCURRENTLY
. Men du kan ikke pakke disse kommandoer ind i en funktion, fordi pr. dokumentation
:
Altså med to separate transaktioner :
CREATE INDEX CONCURRENTLY queries_recent_idx2 ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp; -- your new condition
Så:
DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;
Omdøb eventuelt til gammelt navn:
ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;
2. Delvis indeks med betingelse om "arkiveret" tag
Tilføj en archived
tag til dit bord:
ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;
UPDATE
kolonnen med intervaller efter eget valg at "pensionere" ældre rækker og oprette et indeks som:
CREATE INDEX some_index_name ON queries_query (user_sid, created)
WHERE NOT archived;
Tilføj en matchende betingelse til dine forespørgsler (selvom den virker overflødig) for at tillade den at bruge indekset. Tjek med EXPLAIN ANALYZE
om forespørgselsplanlæggeren fanger - den burde kunne bruge indekset til forespørgsler på en nyere dato. Men det vil ikke forstå mere komplekse forhold, der ikke matcher nøjagtigt.
Du behøver ikke slippe og genskabe indekset, men UPDATE
på bordet kan være dyrere end indeksrekreation, og bordet bliver lidt større.
Jeg ville gå med den første mulighed (indeks rekreation). Faktisk bruger jeg denne løsning i flere databaser. Den anden medfører dyrere opdateringer.
Begge løsninger bevarer deres anvendelighed over tid, ydeevnen forringes langsomt, efterhånden som flere forældede rækker er inkluderet i indekset.