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

Ottimizzazione della query di conteggio per PostgreSQL

PostgreSQL in realtà supporta gli indici GIN sulle colonne dell'array. Sfortunatamente, non sembra essere utilizzabile per NOT ARRAY[...] <@ indexed_col e GIN gli indici non sono comunque adatti per le tabelle aggiornate di frequente.

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

Sfortunatamente, questo mostra che come scritto non possiamo usare l'indice. Se non annulli la condizione, può essere utilizzata, quindi puoi cercare e contare le righe che fanno contenere l'elemento di ricerca (rimuovendo NOT ).

Puoi usare l'indice per contare le voci che fanno contenere il valore target, quindi sottrarre il risultato da un conteggio di tutte le voci. Dal count L'inserimento di tutte le righe in una tabella è piuttosto lento in PostgreSQL (9.1 e precedenti) e richiede una scansione sequenziale che sarà effettivamente più lenta della query corrente. È possibile che su 9.2 una scansione solo indice possa essere utilizzata per contare le righe se hai un indice b-tree su id , nel qual caso potrebbe effettivamente essere OK:

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

È garantito che funzioni peggio della tua versione originale per Pg 9.1 e precedenti, perché oltre al seqscan il tuo originale lo richiede anche necessita di una scansione dell'indice GIN. Ora l'ho testato su 9.2 e sembra utilizzare un indice per il conteggio, quindi vale la pena esplorare per 9.2. Con alcuni dati fittizi meno banali:

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

Nota che un indice GIN come questo rallenterà MOLTO gli aggiornamenti ed è piuttosto lento da creare in primo luogo. Non è adatto per le tabelle che vengono aggiornate molto, come la tua tabella.

Peggio ancora, la query che utilizza questo indice impiega fino al doppio del tempo della query originale e al massimo la metà sullo stesso set di dati. È il peggiore per i casi in cui l'indice non è molto selettivo come ARRAY[1] - 4s vs 2s per la query originale. Dove l'indice è altamente selettivo (es:non molte corrispondenze, come ARRAY[199] ) viene eseguito in circa 1,2 secondi rispetto ai 3 secondi dell'originale. Questo indice semplicemente non vale la pena avere per questa query.

La lezione qui? A volte, la risposta giusta è semplicemente eseguire una scansione sequenziale.

Dal momento che ciò non va bene per le tue percentuali di successo, mantieni una vista materializzata con un trigger come suggerisce @debenhur, o prova a invertire l'array in modo che sia un elenco di parametri che la voce non have in modo da poter utilizzare un indice GiST come suggerisce @maniek.