sql >> Database teknologi >  >> RDS >> PostgreSQL

Tilføj datetime-begrænsning til et PostgreSQL-flerkolonne delvist indeks

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.




  1. Hvordan skriver man dette (venstre join, underforespørgsel) i Laravel 5.1?

  2. MySQL callback - findes der sådan noget?

  3. Kan jeg bruge Postgres-funktionerne til at finde punkter inde i et roterende rektangel af fast størrelse?

  4. Byg dynamisk select-sætning i Oracle 12c