Index max Zeilengrößenfehler

12

Gibt es eine Obergrenze für eine arraySpalte?

Ich erhalte diesen Fehler beim Einfügen in das Array-Feld -

PG::Error: ERROR:  index row size 3480 exceeds maximum 2712 for index "ix_data"

Hier ist meine Tabellendefinition -

create table test_array(id varchar(50), data text[]);

ALTER TABLE test_array ADD PRIMARY KEY (id);

CREATE INDEX ix_data ON test_array USING GIN (data);

Ich benötige einen Index für das Array-Feld, da ich einige Suchvorgänge darauf durchführe.


quelle
Könnte es sein, dass es dataeine Liste von Tags enthält, wie in diesem verwandten Blog-Beitrag von Scott Snyder gezeigt ? In diesem Fall habe ich möglicherweise eine bessere Lösung für Sie.
Erwin Brandstetter
user310525, ich möchte Erwins Vorschlag unterstützen, dass dies auf dba.se besser wäre, wenn Sie bereit sind, dort ein Konto zu erstellen und einen Moderator für die Migration zu kennzeichnen.
Jack sagt, versuchen Sie es mit topanswers.xyz

Antworten:

14

Das Problem

Hier ist ein sehr ähnlicher Fall, der auf pgsql.general diskutiert wird . Es geht um die Einschränkung in einem B-Tree-Index, aber es ist alles das Gleiche, da ein GIN-Index intern einen B-Tree-Index für Schlüssel verwendet und daher auf dieselbe Einschränkung für die Schlüsselgröße stößt (anstelle der Elementgröße in einem einfachen B-Tree) Index).

Ich zitiere das Handbuch zur Implementierung des GIN-Index :

Intern enthält ein GIN-Index einen B-Tree-Index, der über Schlüsseln erstellt wird, wobei jeder Schlüssel ein Element eines oder mehrerer indizierter Elemente ist

In beiden Fällen ist mindestens ein Array-Element in Ihrer Spalte datazu groß, um indiziert zu werden. Wenn dies nur ein einzelner Freak-Wert oder eine Art Unfall ist, können Sie den Wert möglicherweise abschneiden und damit fertig werden.

Für die folgende Demo gehe ich anders aus: viele Langtextwerte im Array.

Einfache Lösung

Sie können Elemente in Ihrem Array datadurch entsprechende Hashwerte ersetzen . Und senden Sie Suchwerte über dieselbe Hash-Funktion. Natürlich möchten Sie Ihre Originale wahrscheinlich zusätzlich irgendwo aufbewahren. Damit kommen wir fast zu meiner zweiten Variante ...

Erweiterte Lösung

Sie können eine Nachschlagetabelle für Array-Elemente mit einer serialSpalte als Ersatz-Primärschlüssel erstellen (praktisch eine radikale Art von Hash-Wert). Dies ist umso interessanter, wenn die beteiligten Elementwerte nicht eindeutig sind:

CREATE TABLE elem (
  elem_id serial NOT NULL PRIMARY KEY
, elem    text UNIQUE NOT NULL
);

Da wir nachschlagen möchten elem, fügen wir einen Index hinzu - diesmal jedoch einen Index für einen Ausdruck mit nur den ersten 10 Zeichen des Langtextes. Dies sollte in den meisten Fällen ausreichen, um eine Suche auf einen oder mehrere Treffer zu beschränken. Passen Sie die Größe an Ihre Datenverteilung an. Oder verwenden Sie eine komplexere Hash-Funktion.

CREATE INDEX elem_elem_left10_idx ON elem(left(elem,10));

Ihre Spalte datawäre dann vom Typ int[]. Ich habe den Tisch in umbenannt dataund das Unheil, das varchar(50)Sie in Ihrem Beispiel hatten, beseitigt:

CREATE TEMP TABLE data(
  data_id serial PRIMARY KEY
, data int[]
);

Jedes Array-Element in databezieht sich auf a elem.elem_id. An diesem Punkt können Sie in Betracht ziehen, die Array-Spalte durch eine n: m-Tabelle zu ersetzen, wodurch Ihr Schema normalisiert wird und Postgres die erzwungene Integrität erzwingen kann. Indizierung und allgemeine Handhabung werden einfacher ...

Aus Leistungsgründen kann die int[]Spalte in Kombination mit einem GIN-Index jedoch überlegen sein. Die Speichergröße ist viel kleiner. In diesem Fall benötigen wir den GIN-Index:

CREATE INDEX data_data_gin_idx ON data USING GIN (data);

Jetzt ist jeder Schlüssel des GIN-Index (= Array-Element) ein integerstatt eines länglichen text. Der Index wird um mehrere Größenordnungen kleiner sein, die Suche wird folglich viel schneller sein.

Der Nachteil: Bevor Sie tatsächlich eine Suche durchführen können, müssen Sie die elem_idvon der Tabelle nachschlagen elem. Mit meinem neu eingeführten Funktionsindex elem_elem_left10_idxwird auch dies viel schneller sein.

Sie können alles in einer einfachen Abfrage erledigen :

SELECT d.*, e.*
FROM   elem e
JOIN   data d ON ARRAY[e.elem_id] <@ d.data
WHERE  left(e.elem, 10) = left('word1234word', 10) -- match index condition
AND    e.elem = 'word1234word';  -- need to recheck, functional index is lossy

Möglicherweise interessiert Sie die Erweiterung intarray, die zusätzliche Operatoren und Operatorklassen bereitstellt.

Voll funktionsfähige Live-Demo auf sqlfiddle.

Erwin Brandstetter
quelle
2

Der Fehler liegt beim Index ix_data, nicht beim text[]Feld. Die maximale Größe für eine Zeile in diesem bestimmten Indextyp ist auf 2712Bytes begrenzt. Wenn Sie Ihren Index löschen und die Einfügung erneut versuchen, sollte dies für Sie funktionieren. Wenn Sie ein größeres Feld indizieren müssen, sollten Sie sich die Volltext-Indizierungsfunktionen von Postgres ansehen.

jcern
quelle
2

Ich habe dies in einer PostGIS-Geografiespalte erhalten. Das lag daran, dass ich den Index versehentlich falsch erstellt habe. Sie sollten den Parameter USING GIST einschließen, wenn Sie solche Indizes erstellen.

Brad Mathews
quelle
Danke - das war es! Wow, soweit geholt. Kann mir Stunden gespart haben. Zumal ich dachte, dass GiST standardmäßig verwendet wird, aber ich habe mich geirrt und versucht, B-Tree zu verwenden.
Jonas