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

La query di Postgresql 9.4 diventa progressivamente più lenta quando si accede a TSTZRANGE con &&

vincolo di esclusione

Ti suggerisco invece di utilizzare un vincolo di esclusione, che è molto più semplice, sicuro e veloce:

È necessario installare il modulo aggiuntivo btree_gist primo. Vedi le istruzioni e la spiegazione in questa risposta correlata:

E devi includere "ParentID" nella tabella "Bar" ridondante, che sarà un piccolo prezzo da pagare. Le definizioni delle tabelle potrebbero assomigliare a questa:

CREATE TABLE "Foo" (
   "FooID"    serial PRIMARY KEY
   "ParentID" int4 NOT NULL REFERENCES "Parent"
   "Details1" varchar
   CONSTRAINT foo_parent_foo_uni UNIQUE ("ParentID", "FooID")  -- required for FK
);

CREATE TABLE "Bar" (
   "ParentID"  int4 NOT NULL,
   "FooID"     int4 NOT NULL REFERENCES "Foo" ("FooID"),
   "Timerange" tstzrange NOT NULL,
   "Detail1"   varchar,
   "Detail2"   varchar,
   CONSTRAINT "Bar_pkey" PRIMARY KEY ("FooID", "Timerange"),
   CONSTRAINT bar_foo_fk
      FOREIGN KEY ("ParentID", "FooID") REFERENCES "Foo" ("ParentID", "FooID"),
   CONSTRAINT bar_parent_timerange_excl
      EXCLUDE USING gist ("ParentID" WITH =, "Timerange" WITH &&)
);

Ho anche cambiato il tipo di dati per "Bar"."FooID" da int8 a int4 . Fa riferimento a "Foo"."FooID" , che è un serial , ovvero int4 . Utilizza il tipo di corrispondenza int4 (o solo integer ) per diversi motivi, uno dei quali è la prestazione.

Non hai più bisogno di un trigger (almeno non per questa attività) e non crei l'indice "Bar_FooID_Timerange_idx" non più, poiché è creato implicitamente dal vincolo di esclusione.

Un indice btree su ("ParentID", "FooID") molto probabilmente sarà utile, però:

CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");

Correlati:

Ho scelto UNIQUE ("ParentID", "FooID") e non il contrario per un motivo, poiché esiste un altro indice con "FooID" all'inizio in una delle due tabelle:

A parte:Non uso mai CaMeL con doppie virgolette -identificatori di casi a Postgres. Lo faccio qui solo per rispettare il tuo layout.

Evita la colonna ridondante

Se non puoi o non vuoi includere "Bar"."ParentID" ridondante, c'è un altro rogue modo - a condizione che "Foo"."ParentID" è mai aggiornato . Assicurati di farlo, ad esempio con un trigger.

Puoi falsificare un IMMUTABLE funzione:

CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
  RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
  LANGUAGE sql IMMUTABLE;

Ho qualificato lo schema del nome della tabella per esserne sicuro, assumendo public . Adattati al tuo schema.

Altro:

Quindi usalo nel vincolo di esclusione:

   CONSTRAINT bar_parent_timerange_excl
      EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)

Durante il salvataggio di un int4 ridondante colonna, il vincolo sarà più costoso da verificare e l'intera soluzione dipende da più precondizioni.

Gestire i conflitti

Potresti avvolgere INSERT e UPDATE in una funzione plpgsql e intercetta possibili eccezioni dal vincolo di esclusione (23P01 exclusion_violation ) per gestirlo in qualche modo.

INSERT ...

EXCEPTION
    WHEN exclusion_violation
    THEN  -- handle conflict

Esempio di codice completo:

Gestire i conflitti in Postgres 9.5

In Postgres 9,5 puoi gestire INSERT direttamente con la nuova implementazione "UPSERT". La documentazione:

Tuttavia:

Ma puoi ancora usare ON CONFLICT DO NOTHING , evitando così possibili exclusion_violation eccezioni. Controlla solo se alcune righe sono state effettivamente aggiornate, il che è più economico:

INSERT ... 
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;

IF NOT FOUND THEN
   -- handle conflict
END IF;

Questo esempio limita il controllo al vincolo di esclusione specificato. (Ho chiamato il vincolo esplicitamente per questo scopo nella definizione della tabella sopra.) Altre possibili eccezioni non vengono rilevate.