sql >> Base de Datos >  >> RDS >> PostgreSQL

La consulta de Postgresql 9.4 se vuelve progresivamente más lenta cuando se une a TSTZRANGE con &&

Restricción de exclusión

Le sugiero que use una restricción de exclusión en su lugar, que es mucho más simple, más segura y más rápida:

Debe instalar el módulo adicional btree_gist primero. Consulte las instrucciones y la explicación en esta respuesta relacionada:

Y debe incluir "ParentID" en la tabla "Bar" redundantemente, lo que será un pequeño precio a pagar. Las definiciones de las tablas podrían tener este aspecto:

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 &&)
);

También cambié el tipo de datos para "Bar"."FooID" de int8 a int4 . Hace referencia a "Foo"."FooID" , que es un serial , es decir, int4 . Utilice el tipo de coincidencia int4 (o simplemente integer ) por varias razones, una de ellas es el rendimiento.

Ya no necesita un activador (al menos no para esta tarea), y no crea el índice "Bar_FooID_Timerange_idx" más, ya que se crea implícitamente por la restricción de exclusión.

Un índice btree en ("ParentID", "FooID") aunque lo más probable es que sea útil:

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

Relacionado:

Elegí UNIQUE ("ParentID", "FooID") y no al revés por una razón, ya que hay otro índice con "FooID" inicial en cualquier tabla:

Aparte:Nunca uso CaMeL entre comillas dobles -identificadores de casos en Postgres. Solo lo hago aquí para cumplir con su diseño.

Evite la columna redundante

Si no puede o no quiere incluir "Bar"."ParentID" redundantemente, hay otro pícaro - con la condición de que "Foo"."ParentID" nunca se actualiza . Asegúrate de eso, con un activador, por ejemplo.

Puedes falsificar un IMMUTABLE función:

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

Califiqué el esquema del nombre de la tabla para asegurarme, asumiendo public . Adaptarse a su esquema.

Más:

Luego utilícelo en la restricción de exclusión:

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

Al guardar un int4 redundante columna, la restricción será más costosa de verificar y toda la solución depende de más condiciones previas.

Manejar conflictos

Podría envolver INSERT y UPDATE en una función plpgsql y atrape posibles excepciones de la restricción de exclusión (23P01 exclusion_violation ) para manejarlo de alguna manera.

INSERT ...

EXCEPTION
    WHEN exclusion_violation
    THEN  -- handle conflict

Ejemplo de código completo:

Manejar conflictos en Postgres 9.5

En Postgres 9.5 puedes manejar INSERT directamente con la nueva implementación "UPSERT". La documentación:

Sin embargo:

Pero aún puedes usar ON CONFLICT DO NOTHING , evitando así posibles exclusion_violation excepciones Simplemente verifique si alguna fila se actualizó realmente, lo cual es más económico:

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

IF NOT FOUND THEN
   -- handle conflict
END IF;

Este ejemplo restringe la verificación a la restricción de exclusión dada. (Nombré la restricción explícitamente para este propósito en la definición de la tabla anterior). No se detectan otras posibles excepciones.