sql >> Database >  >> RDS >> PostgreSQL

Optimalisatie van telquery voor PostgreSQL

PostgreSQL ondersteunt eigenlijk GIN-indexen op matrixkolommen. Helaas lijkt het niet bruikbaar voor NOT ARRAY[...] <@ indexed_col , en GIN indexen zijn sowieso niet geschikt voor tabellen die regelmatig worden bijgewerkt.

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

Helaas laat dit zien dat we de index zoals geschreven niet kunnen gebruiken. Als u de voorwaarde niet ontkent, kan deze worden gebruikt, zodat u rijen kunt zoeken en tellen die doen het zoekelement bevatten (door NOT te verwijderen ).

U kunt de index gebruiken om items te tellen die doen de doelwaarde bevatten en trek dat resultaat vervolgens af van een telling van alle vermeldingen. Sinds count alle rijen in een tabel zijn vrij traag in PostgreSQL (9.1 en ouder) en vereist een sequentiële scan. Dit zal in feite langzamer zijn dan uw huidige query. Het is mogelijk dat op 9.2 een alleen-index scan kan worden gebruikt om de rijen te tellen als je een b-tree index op id hebt , in welk geval dit eigenlijk in orde zou kunnen zijn:

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

Het presteert gegarandeerd slechter dan uw originele versie voor Pg 9.1 en lager, want naast de seqscan vereist uw origineel het ook heeft een GIN-indexscan nodig. Ik heb dit nu getest op 9.2 en het lijkt een index te gebruiken voor de telling, dus het is de moeite waard om voor 9.2 te onderzoeken. Met wat minder triviale dummy-gegevens:

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

Merk op dat een GIN-index als deze de updates VEEL zal vertragen en in de eerste plaats vrij traag is om te maken. Het is niet geschikt voor tabellen die veel worden bijgewerkt - zoals uw tabel.

Erger nog, de zoekopdracht die deze index gebruikt, duurt tot twee keer zo lang als uw oorspronkelijke zoekopdracht en op zijn best half zo lang op dezelfde dataset. Het is het ergst voor gevallen waarin de index niet erg selectief is, zoals ARRAY[1] - 4s vs 2s voor de oorspronkelijke zoekopdracht. Waar de index zeer selectief is (dwz:niet veel overeenkomsten, zoals ARRAY[199] ) het loopt in ongeveer 1,2 seconden versus de 3s van het origineel. Deze index is gewoon niet de moeite waard voor deze zoekopdracht.

De les hier? Soms is het juiste antwoord gewoon een sequentiële scan te doen.

Aangezien dat niet goed is voor uw hitrates, moet u ofwel een gematerialiseerde weergave behouden met een trigger zoals @debenhur suggereert, of proberen de array om te keren tot een lijst met parameters die de invoer niet doet. hebben zodat je een GiST-index kunt gebruiken zoals @maniek suggereert.



  1. Een tabel bijwerken vanuit een andere tabel met meerdere kolommen in sqlalchemy

  2. Het verzenden van HTML-e-mail resulteert in e-mail met HTML-bron (Codeigniter-e-mailklasse)

  3. Welsprekend de rij met de maximale waarde vinden met groeperen

  4. Stel het veld Auto Increment in vanaf 1000 in migratie laravel 5.1