it-swarm.com.de

Welcher Index soll mit vielen doppelten Werten verwendet werden?

Nehmen wir einige Annahmen an:

Ich habe einen Tisch, der so aussieht:

 a | b
---+---
 a | -1
 a | 17
  ...
 a | 21
 c | 17
 c | -3
  ...
 c | 22

Fakten zu meinem Set:

  • Die Größe des gesamten Tisches beträgt ~ 1010 Reihen.

  • Ich habe ~ 100.000 Zeilen mit dem Wert a in der Spalte a, ähnlich wie bei anderen Werten (z. B. c).

  • Das bedeutet ~ 100.000 unterschiedliche Werte in Spalte 'a'.

  • Die meisten meiner Abfragen lesen alle oder die meisten Werte für einen bestimmten Wert in a, z. select sum(b) from t where a = 'c'.

  • Die Tabelle ist so geschrieben, dass aufeinanderfolgende Werte physisch nahe beieinander liegen (entweder in der angegebenen Reihenfolge oder wir nehmen an, dass CLUSTER für diese Tabelle und Spalte a verwendet wurde ).

  • Die Tabelle wird selten oder nie aktualisiert, wir sind nur über die Lesegeschwindigkeit besorgt.

  • Die Tabelle ist relativ eng (z. B. ~ 25 Byte pro Tupel, + 23 Byte Overhead).

Die Frage ist nun, welche Art von Index sollte ich verwenden? Mein Verständnis ist:

  • BTree Mein Problem hier ist, dass der BTree-Index riesig sein wird, da er meines Wissens doppelte Werte speichert (muss es, da es kann ' t davon ausgehen, dass die Tabelle physisch sortiert ist). Wenn der BTree riesig ist, muss ich am Ende sowohl den Index als auch die Teile der Tabelle lesen, auf die der Index zeigt. (Wir können fillfactor = 100 Verwenden, um die Größe des Index etwas zu verringern.)

  • [~ # ~] brin [~ # ~] Mein Verständnis ist, dass ich hier einen kleinen Index haben kann, auf Kosten des Lesens nutzloser Seiten. Die Verwendung eines kleinen pages_per_range Bedeutet, dass der Index größer ist (was bei BRIN ein Problem darstellt, da ich den gesamten Index lesen muss). Wenn Sie einen großen pages_per_range Haben, bedeutet dies, dass ich viel lesen werde nutzlose Seiten. Gibt es eine Zauberformel, um einen guten Wert von pages_per_range Zu finden, der diese Kompromisse berücksichtigt?

  • GIN/Gist Ich bin mir nicht sicher, ob diese hier relevant sind, da sie hauptsächlich für die Volltextsuche verwendet werden, aber ich höre auch, dass sie gut umgehen können mit doppelten Schlüsseln. Würde hier entweder ein GIN oder Gist Index helfen?

Eine andere Frage ist, ob Postgres die Tatsache verwendet, dass eine Tabelle im Abfrageplaner CLUSTERed ist (vorausgesetzt, es werden keine Aktualisierungen vorgenommen) (z. B. durch binäre Suche nach den relevanten Start-/Endseiten). Kann ich einfach alle meine Spalten in einem BTree speichern und die Tabelle ganz löschen (oder etwas Äquivalentes erreichen, ich glaube, das sind Clustered-Indizes in SQL Server)? Gibt es einen hybriden BTree/BRIN-Index, der hier helfen würde?

Ich möchte lieber vermeiden, Arrays zum Speichern meiner Werte zu verwenden, da meine Abfrage auf diese Weise weniger lesbar wird (ich verstehe, dass dies die Kosten für den Overhead von 23 Bytes pro Tupel reduzieren würde, indem die Anzahl der Tupel verringert wird).

14
foo

BTree

Mein Problem hierbei ist, dass der BTree-Index sehr groß sein wird, da er möglicherweise doppelte Werte speichert (dies ist auch der Fall, da nicht davon ausgegangen werden kann, dass die Tabelle physisch sortiert ist). Wenn der BTree riesig ist, muss ich am Ende sowohl den Index als auch die Teile der Tabelle lesen, auf die der Index auch zeigt ...

Nicht unbedingt - Ein btree-Index von 'covering' ist die schnellste Lesezeit. Wenn dies alles ist, was Sie möchten (dh wenn Sie sich den zusätzlichen Speicher leisten können), ist dies die beste Wahl .

BRIN

Mein Verständnis ist, dass ich hier einen kleinen Index haben kann, auf Kosten des Lesens nutzloser Seiten. Die Verwendung eines kleinen pages_per_range Bedeutet, dass der Index größer ist (was bei BRIN ein Problem darstellt, da ich den gesamten Index lesen muss). Wenn Sie einen großen pages_per_range Haben, bedeutet dies, dass ich viel lesen werde nutzlose Seiten.

Wenn Sie sich den Speicheraufwand eines abdeckenden Btree-Index nicht leisten können, ist BRIN ideal für Sie, da bereits Clustering vorhanden ist (dies ist entscheidend damit BRIN nützlich ist). BRIN-Indizes sind winzig , sodass sich wahrscheinlich alle Seiten im Speicher befinden, wenn Sie einen geeigneten Wert von pages_per_range Wählen.

Gibt es eine Zauberformel, um einen guten Wert für pages_per_range zu finden, der diese Kompromisse berücksichtigt?

Keine Zauberformel, aber beginnen Sie mit pages_per_rangeetwas weniger als der durchschnittlichen Größe (in Seiten), die vom durchschnittlichen a -Wert belegt wird. Sie versuchen wahrscheinlich zu minimieren: (Anzahl der gescannten BRIN-Seiten) + (Anzahl der gescannten Heap-Seiten) für eine typische Abfrage. Suchen Sie im Ausführungsplan nach Heap Blocks: lossy=n Nach pages_per_range=1 Und vergleichen Sie diese mit anderen Werten für pages_per_range - d. H. Sehen Sie, wie viele unnötige Heap-Blöcke gescannt werden.

GIN/Gist

Ich bin mir nicht sicher, ob diese hier relevant sind, da sie hauptsächlich für die Volltextsuche verwendet werden, aber ich höre auch, dass sie gut mit doppelten Schlüsseln umgehen können. Würde hier entweder ein GIN/Gist Index helfen?

GIN ist vielleicht eine Überlegung wert, aber wahrscheinlich nicht Gist - aber wenn die natürliche Clusterbildung wirklich gut ist, ist BRIN wahrscheinlich die bessere Wahl.

Hier ist ein Beispielvergleich zwischen den verschiedenen Indextypen für Dummy-Daten, ähnlich wie bei Ihnen:

tabelle und Indizes:

create table foo(a,b,c) as
select *, lpad('',20)
from (select chr(g) a from generate_series(97,122) g) a
     cross join (select generate_series(1,100000) b) b
order by a;
create index foo_btree_covering on foo(a,b);
create index foo_btree on foo(a);
create index foo_gin on foo using gin(a);
create index foo_brin_2 on foo using brin(a) with (pages_per_range=2);
create index foo_brin_4 on foo using brin(a) with (pages_per_range=4);
vacuum analyze;

beziehungsgrößen:

select relname "name", pg_size_pretty(siz) "size", siz/8192 pages, (select count(*) from foo)*8192/siz "rows/page"
from( select relname, pg_relation_size(C.oid) siz
      from pg_class c join pg_namespace n on n.oid = c.relnamespace
      where nspname = current_schema ) z;
 Name | Größe | Seiten | Zeilen/Seite 
: -------------- : ------ | ----: | --------: 
 foo | 149 MB | 19118 | 135 
 Foo_btree_covering | 56 MB | 7132 | 364 
 Foo_btree | 56 MB | 7132 | 364 
 Foo_gin | 2928 kB | 366 | 7103 
 Foo_brin_2 | 264 kB | 33 | 78787 
 Foo_brin_4 | 136 kB | 17 | 152941 

btree abdecken:

explain analyze select sum(b) from foo where a='a';
 | ABFRAGEPLAN | 
 | : --------------------------------------------- -------------------------------------------------- --------------------------------------- | 
 | Aggregat (Kosten = 3282,57..3282,58 Zeilen = 1 Breite = 8) (tatsächliche Zeit = 45,942..45,942 Zeilen = 1 Schleifen = 1) | 
 | -> Nur Index Scannen mit foo_btree_covering auf foo (Kosten = 0,43..3017,80 Zeilen = 105907 Breite = 4) (tatsächliche Zeit = 0,038..27,286 Zeilen = 100000 Schleifen = 1) | 
 | Index Cond: (a = 'a' :: text) | 
 | Heap Fetches: 0 | 
 | Planungszeit: 0,099 ms | 
 | Ausführungszeit: 45.968 ms | 

einfacher btree:

drop index foo_btree_covering;
explain analyze select sum(b) from foo where a='a';
 | ABFRAGEPLAN | 
 | : --------------------------------------------- -------------------------------------------------- ----------------------------- | 
 | Aggregat (Kosten = 4064,57..4064,58 Zeilen = 1 Breite = 8) (tatsächliche Zeit = 54,242..54,242 Zeilen = 1 Schleifen = 1) | 
 | -> Index-Scan mit foo_btree auf foo (Kosten = 0,43..3799,80 Zeilen = 105907 Breite = 4) (tatsächliche Zeit = 0,037..33,084 Zeilen = 100000 Schleifen = 1) | 
 | Index Cond: (a = 'a' :: text) | 
 | Planungszeit: 0,135 ms | 
 | Ausführungszeit: 54.280 ms | 

BRIN pages_per_range = 4:

drop index foo_btree;
explain analyze select sum(b) from foo where a='a';
 | ABFRAGEPLAN | 
 | : --------------------------------------------- -------------------------------------------------- ----------------------------- | 
 | Aggregat (Kosten = 21595,38..21595,39 Zeilen = 1 Breite = 8) (tatsächliche Zeit = 52,455..52,455 Zeilen = 1 Schleifen = 1) | 
 | -> Bitmap-Heap-Scan auf foo (Kosten = 888,78..21330,61 Zeilen = 105907 Breite = 4) (tatsächliche Zeit = 2,738..31,967 Zeilen = 100000 Schleifen = 1) | 
 | Überprüfen Sie erneut Cond: (a = 'a' :: text) | 
 | Durch Indexprüfung entfernte Zeilen erneut prüfen: 96 | 
 | Heap-Blöcke: verlustbehaftet = 736 | 
 | -> Bitmap-Index-Scan auf foo_brin_4 (Kosten = 0,00..862,30 Zeilen = 105907 Breite = 0) (tatsächliche Zeit = 2,720..2,720 Zeilen = 7360 Schleifen = 1) | 
 | Index Cond: (a = 'a' :: text) | 
 | Planungszeit: 0,101 ms | 
 | Ausführungszeit: 52.501 ms | 

BRIN pages_per_range = 2:

drop index foo_brin_4;
explain analyze select sum(b) from foo where a='a';
 | ABFRAGEPLAN | 
 | : --------------------------------------------- -------------------------------------------------- ----------------------------- | 
 | Aggregat (Kosten = 21659.38..21659.39 Zeilen = 1 Breite = 8) (tatsächliche Zeit = 53.971..53.971 Zeilen = 1 Schleifen = 1) | 
 | -> Bitmap-Heap-Scan auf foo (Kosten = 952,78..21394,61 Zeilen = 105907 Breite = 4) (tatsächliche Zeit = 5,286..33,492 Zeilen = 100000 Schleifen = 1) | 
 | Überprüfen Sie erneut Cond: (a = 'a' :: text) | 
 | Durch Indexprüfung entfernte Zeilen erneut prüfen: 96 | 
 | Heap-Blöcke: verlustbehaftet = 736 | 
 | -> Bitmap-Index-Scan auf foo_brin_2 (Kosten = 0,00..926,30 Zeilen = 105907 Breite = 0) (tatsächliche Zeit = 5,275..5,275 Zeilen = 7360 Schleifen = 1) | 
 | Index Cond: (a = 'a' :: text) | 
 | Planungszeit: 0,095 ms | 
 | Ausführungszeit: 54.016 ms | 

GIN:

drop index foo_brin_2;
explain analyze select sum(b) from foo where a='a';
 | ABFRAGEPLAN | 
 | : --------------------------------------------- -------------------------------------------------- ------------------------------ | 
 | Aggregat (Kosten = 21687.38..21687.39 Zeilen = 1 Breite = 8) (tatsächliche Zeit = 55.331..55.331 Zeilen = 1 Schleifen = 1) | 
 | -> Bitmap-Heap-Scan auf foo (Kosten = 980,78..21422,61 Zeilen = 105907 Breite = 4) (tatsächliche Zeit = 12,377..33,956 Zeilen = 100000 Schleifen = 1) | 
 | Überprüfen Sie erneut Cond: (a = 'a' :: text) | 
 | Heap-Blöcke: genau = 736 | 
 | -> Bitmap-Index-Scan auf foo_gin (Kosten = 0,00..954,30 Zeilen = 105907 Breite = 0) (tatsächliche Zeit = 12,271..12,271 Zeilen = 100000 Schleifen = 1) | 
 | Index Cond: (a = 'a' :: text) | 
 | Planungszeit: 0,118 ms | 
 | Ausführungszeit: 55.366 ms | 

dbfiddle --- (hier

Neben btree und brin , die die sinnvollsten Optionen scheinen, einige andere, exotische Optionen, die es wert sein könnten, untersucht zu werden - sie könnten in Ihrem Fall hilfreich sein oder nicht:

  • INCLUDE Indizes . Sie werden - hoffentlich - irgendwann im September 2017 in der nächsten Hauptversion (10) von Postgres erscheinen. Ein Index für (a) INCLUDE (b) hat die gleiche Struktur wie ein Index für (a), enthält jedoch in die Blattseiten, alle Werte von b (aber ungeordnet). Das heißt, Sie können es beispielsweise nicht für SELECT * FROM t WHERE a = 'a' AND b = 2 ; verwenden. Der Index kann verwendet werden, aber während ein (a,b) Index die übereinstimmenden Zeilen mit einer einzigen Suche findet, muss der Include-Index die (möglicherweise 100 KB wie in Ihrem Fall) Werte durchlaufen, die mit dem a = 'a' und überprüfen Sie die b -Werte.
    Andererseits ist der Index etwas weniger breit als der (a,b) Index und Sie benötigen die Reihenfolge auf b nicht, damit Ihre Abfrage SUM(b). Sie können beispielsweise auch (a) INCLUDE (b,c,d) verwenden, das für ähnliche Abfragen verwendet werden kann, die in allen drei Spalten zusammengefasst sind.

  • Gefilterte (Teil-) Indizes . Ein Vorschlag, der ein bisschen verrückt klingt* * zunaechst:

    CREATE INDEX flt_a  ON t (b) WHERE (a = 'a') ;
    ---
    CREATE INDEX flt_xy ON t (b) WHERE (a = 'xy') ;
    

    Ein Index für jeden a Wert. In Ihrem Fall rund 100.000 Indizes. Obwohl dies sehr viel klingt, bedenken Sie, dass jeder Index sowohl in der Größe (Anzahl der Zeilen) als auch in der Breite sehr klein ist (da nur b -Werte gespeichert werden). In allen anderen Aspekten fungiert es (die 100K-Indizes zusammen) als B-Tree-Index für (a,b), während der Platz eines (b) -Index verwendet wird.
    Nachteil ist, dass Sie sie jedes Mal selbst erstellen und pflegen müssen, wenn der Tabelle ein neuer Wert von a hinzugefügt wird. Da Ihre Tabelle ziemlich stabil ist, ohne viele (oder keine) Einfügungen/Aktualisierungen, scheint dies kein Problem zu sein.

  • Übersichtstabellen. Da die Tabelle ziemlich stabil ist, können Sie jederzeit eine Übersichtstabelle mit den am häufigsten benötigten Aggregaten erstellen und füllen (sum(b), sum(c), sum(d), avg(b), count(distinct b) usw.). Es ist klein (nur 100.000 Zeilen) und muss nur einmal ausgefüllt und aktualisiert werden, wenn Zeilen in die Haupttabelle eingefügt/aktualisiert/gelöscht werden.

*: Idee von dieser Firma kopiert, die 10 Millionen Indizes in ihrem Produktionssystem ausführt: The Heap: 10 Millionen Postgresql-Indizes in der Produktion ausführen (und zählen) .

6
ypercubeᵀᴹ