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

Optimering af tælleforespørgsel til PostgreSQL

PostgreSQL understøtter faktisk GIN-indekser på array-kolonner. Desværre ser det ikke ud til at kunne bruges til NOT ARRAY[...] <@ indexed_col og GIN Indekser er alligevel uegnede til hyppigt opdaterede tabeller.

Demo:

CREATE TABLE arrtable (id integer primary key, array_column integer[]);

INSERT INTO arrtable(1, ARRAY[1,2,3,4]);

CREATE INDEX arrtable_arraycolumn_gin_arr_idx
ON arrtable USING GIN(array_column);

-- Use the following *only* for testing whether Pg can use an index
-- Do not use it in production.
SET enable_seqscan = off;

explain (buffers, analyze) select count(id) 
from arrtable 
where not (ARRAY[1] <@ arrtable.array_column);

Desværre viser dette, at vi som skrevet ikke kan bruge indekset. Hvis du ikke afviser betingelsen, kan den bruges, så du kan søge efter og tælle rækker, der gør indeholde søgeelementet (ved at fjerne NOT ).

Du kan bruge indekset til at tælle poster, der gør indeholder målværdien, og træk derefter dette resultat fra en optælling af alle poster. Siden count at alle rækker i en tabel er ret langsom i PostgreSQL (9.1 og ældre) og kræver en sekventiel scanning, dette vil faktisk være langsommere end din nuværende forespørgsel. Det er muligt, at på 9.2 en indeks-kun-scanning kan bruges til at tælle rækkerne, hvis du har et b-træ indeks på id , i så fald kan dette faktisk være OK:

SELECT (
  SELECT count(id) FROM arrtable
) - (
  SELECT count(id) FROM arrtable 
  WHERE (ARRAY[1] <@ arrtable.array_column)
);

Det er garanteret at yde dårligere end din originale version til Pg 9.1 og derunder, for ud over seqscanningen kræver din original det også har brug for en GIN-indeksscanning. Jeg har nu testet dette på 9.2, og det ser ud til at bruge et indeks for optællingen, så det er værd at udforske for 9.2. Med nogle mindre trivielle dummy-data:

drop index arrtable_arraycolumn_gin_arr_idx ;
truncate table arrtable;
insert into arrtable (id, array_column)
select s, ARRAY[1,2,s,s*2,s*3,s/2,s/4] FROM generate_series(1,1000000) s;
CREATE INDEX arrtable_arraycolumn_gin_arr_idx
ON arrtable USING GIN(array_column);

Bemærk, at et GIN-indeks som dette vil sænke opdateringerne MEGET, og det er ret langsomt at oprette i første omgang. Det er ikke egnet til borde, der bliver opdateret meget overhovedet - som dit bord.

Hvad værre er, forespørgslen, der bruger dette indeks, tager op til dobbelt så lang tid som din oprindelige forespørgsel og i bedste fald halvt så lang tid på samme datasæt. Det er værst i tilfælde, hvor indekset ikke er særlig selektivt som ARRAY[1] - 4s vs 2s for den oprindelige forespørgsel. Hvor indekset er meget selektivt (dvs.:ikke mange matches, såsom ARRAY[199] ) den kører på omkring 1,2 sekunder i forhold til originalens 3s. Dette indeks er simpelthen ikke værd at have til denne forespørgsel.

Lektionen her? Nogle gange er det rigtige svar bare at lave en sekventiel scanning.

Da det ikke gør for dine hitrater, skal du enten bevare en materialiseret visning med en trigger, som @debenhur foreslår, eller prøve at invertere arrayet til en liste over parametre, som indgangen ikke gør. har, så du kan bruge et GiST-indeks som @maniek foreslår.



  1. Postgres-udløser efter indstik med adgang til NYT

  2. Funktionen udføres hurtigere uden STRICT modifikator?

  3. Ugyldig PathExpression. Skal være et StateFieldPathExpression

  4. Returner hver n'te række fra databasen ved hjælp af ActiveRecord i rails