PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Optimisation de la requête de comptage pour PostgreSQL

PostgreSQL prend en charge les index GIN sur les colonnes de tableau. Malheureusement, il ne semble pas être utilisable pour NOT ARRAY[...] <@ indexed_col , et GIN les index ne conviennent de toute façon pas aux tables fréquemment mises à jour.

Démo :

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

Malheureusement, cela montre que tel qu'il est écrit, nous ne pouvons pas utiliser l'index. Si vous ne niez pas la condition, elle peut être utilisée, vous pouvez donc rechercher et compter les lignes qui font contenir l'élément de recherche (en supprimant NOT ).

Vous pouvez utiliser l'index pour compter les entrées qui font contenir la valeur cible, puis soustraire ce résultat d'un décompte de toutes les entrées. Depuis count ing toutes les lignes d'une table est assez lent dans PostgreSQL (9.1 et versions antérieures) et nécessite une analyse séquentielle, ce sera en fait plus lent que votre requête actuelle. Il est possible que sur 9.2, un parcours d'index uniquement puisse être utilisé pour compter les lignes si vous avez un index b-tree sur id , auquel cas cela pourrait en fait être OK :

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

Il est garanti qu'il fonctionnera moins bien que votre version d'origine pour la page 9.1 et les versions inférieures, car en plus du seqscan, votre original l'exige également a besoin d'un scan d'index GIN. J'ai maintenant testé cela sur 9.2 et il semble utiliser un index pour le décompte, il vaut donc la peine d'explorer pour 9.2. Avec quelques données factices moins triviales :

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

Notez qu'un index GIN comme celui-ci ralentira BEAUCOUP les mises à jour et est assez lent à créer en premier lieu. Il n'est pas adapté aux tableaux qui sont souvent mis à jour - comme votre tableau.

Pire, la requête utilisant cet index prend jusqu'à deux fois plus de temps que votre requête d'origine et au mieux la moitié moins sur le même ensemble de données. C'est pire pour les cas où l'index n'est pas très sélectif comme ARRAY[1] - 4s vs 2s pour la requête d'origine. Où l'index est très sélectif (c'est-à-dire :peu de correspondances, comme ARRAY[199] ) il s'exécute en environ 1,2 seconde par rapport aux 3 secondes de l'original. Cet index ne vaut tout simplement pas la peine d'être utilisé pour cette requête.

La leçon ici? Parfois, la bonne réponse consiste simplement à effectuer une analyse séquentielle.

Comme cela ne fonctionnera pas pour vos taux de réussite, conservez une vue matérialisée avec un déclencheur comme le suggère @debenhur, ou essayez d'inverser le tableau pour qu'il soit une liste de paramètres que l'entrée ne fait pas vous pouvez donc utiliser un index GiST comme le suggère @maniek.