PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

A consulta do Postgresql 9.4 fica progressivamente mais lenta ao unir TSZRANGE com &&

Restrição de exclusão


Sugiro que você use uma restrição de exclusão, que é muito mais simples, segura e rápida:

Você precisa instalar o módulo adicional btree_gist primeiro. Veja instruções e explicações nesta resposta relacionada:

E você precisa incluir "ParentID" na tabela "Barra" redundante, o que será um pequeno preço a pagar. As definições de tabela podem ser assim:
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 &&)
);

Também alterei o tipo de dados para "Bar",."FooID" de int8 para int4 . Faz referência a "Foo","FooID" , que é um serial , ou seja, int4 . Use o tipo de correspondência int4 (ou apenas inteiro ) por vários motivos, sendo um deles o desempenho.

Você não precisa mais de um gatilho (pelo menos não para esta tarefa) e não cria o índice "Bar_FooID_Timerange_idx" mais, já que é criado implicitamente pela restrição de exclusão.

Um índice btree em ("ParentID", "FooID") provavelmente será útil, no entanto:
CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");

Relacionado:

Eu escolhi UNIQUE ("ParentID", "FooID") e não o contrário por um motivo, já que existe outro índice com "FooID" à esquerda em qualquer tabela:

Além:Eu nunca uso CaMeL entre aspas duplas -identificadores de caso em Postgres. Eu só faço isso aqui para cumprir o seu layout.

Evite coluna redundante


Se você não puder ou não quiser incluir "Bar",."ParentID" redundantemente, há outro trapaceiro maneira - com a condição de que "Foo",."ParentID" é nunca atualizado . Certifique-se disso, com um gatilho, por exemplo.

Você pode falsificar um IMMUTABLE função:
CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
  RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
  LANGUAGE sql IMMUTABLE;

Eu qualifiquei o nome da tabela para ter certeza, assumindo public . Adapte-se ao seu esquema.

Mais:

Em seguida, use-o na restrição de exclusão:
   CONSTRAINT bar_parent_timerange_excl
      EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)

Ao salvar um int4 redundante coluna, a restrição será mais cara para verificar e toda a solução depende de mais pré-condições.

Lidar com conflitos


Você pode envolver INSERT e ATUALIZAR em uma função plpgsql e interceptar possíveis exceções da restrição de exclusão (23P01 exclude_violation ) para lidar com isso de alguma forma.
INSERT ...

EXCEPTION
    WHEN exclusion_violation
    THEN  -- handle conflict

Exemplo de código completo:

Lidar com conflitos no Postgres 9.5


No Postgres 9.5 você pode lidar com INSERT diretamente com a nova implementação "UPSERT". A documentação:

No entanto:

Mas você ainda pode usar ON CONFLICT DO NOTHING , evitando assim possíveis exclusion_violation exceções. Basta verificar se alguma linha foi realmente atualizada, o que é mais barato:
INSERT ... 
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;

IF NOT FOUND THEN
   -- handle conflict
END IF;

Este exemplo restringe a verificação à restrição de exclusão fornecida. (Eu nomeei a restrição explicitamente para este propósito na definição da tabela acima.) Outras exceções possíveis não são capturadas.