Beschreibung
PostgreSQL 9.6 unter Linux, tags_tmp
Tabellengröße ~ 30 GB (10 Millionen Zeilen), tags
ist a text[]
und hat nur 6 Werte.
tags_tmp(id int, tags text[], maker_date timestamp, value text)
id tags maker_date value
1 {a,b,c} 2016-11-09 This is test
2 {a} 2016-11-08 This is test
3 {b,c} 2016-11-07 This is test
4 {c} 2016-11-06 This is test
5 {d} 2016-11-05 This is test
Ich brauche auf Daten mit Filter abzurufen tags
sowie order by
auf maker_date desc
. Kann ich für beide tags & maker_date desc
Spalten einen Index erstellen ?
Wenn nicht, könnten Sie andere Ideen vorschlagen?
Abfragebeispiel
select id, tags, maker_date, value
from tags_tmp
where tags && array['a','b']
order by maker_date desc
limit 5 offset 0
SQL-Code:
create index idx1 on tags_tmp using gin (tags);
create index idx2 on tags_tmp using btree(maker_date desc);
explain (analyse on, costs on, verbose)
select id, tags, maker_date, value
from tags_tmp
where tags && array['funny','inspiration']
order by maker_date desc
limit 5 offset 0 ;
Ergebnis erklären:
Limit (cost=233469.63..233469.65 rows=5 width=116) (actual time=801.482..801.483 rows=5 loops=1)
Output: id, tags, maker_date, value
-> Sort (cost=233469.63..234714.22 rows=497833 width=116) (actual time=801.481..801.481 rows=5 loops=1)
Output: id, tags, maker_date, value
Sort Key: tags_tmp.maker_date DESC
Sort Method: top-N heapsort Memory: 25kB
-> Bitmap Heap Scan on public.tags_tmp (cost=6486.58..225200.81 rows=497833 width=116) (actual time=212.982..696.650 rows=366392 loops=1)
Output: id, tags, maker_date, value
Recheck Cond: (tags_tmp.tags && '{funny,inspiration}'::text[])
Heap Blocks: exact=120034
-> Bitmap Index Scan on idx1 (cost=0.00..6362.12 rows=497882 width=0) (actual time=171.742..171.742 rows=722612 loops=1)
Index Cond: (tags_tmp.tags && '{funny,inspiration}'::text[])
Planning time: 0.185 ms
Execution time: 802.128 ms
Mehr Informationen
Ich habe mit der Verwendung eines Teilindex für nur ein Tag getestet, natürlich ist es schneller. Aber ich habe viele Tags , zum Beispiel : create index idx_tmp on tags_tmp using btree (maker_date desc) where (tags && array['tag1') or tags && array['tag2'] or ... or tags && array['tag6']
. Und ich habe zwischen tags && array['tag1']
und getestet 'tag1' = any(tags)
, die Leistung ist gleich.
text[]
hat nur 6 Werte =a, b, c, d, e, f
. Zum Beispiel:tags={a,b,c}, tags={a}, tags={a,c}, tags={a,b,c,d,e,f}, tags={b,f}
und so weiter. Aber es kann keinen Wert habeng->z, A-Z
und so weiter.create table tags_tmp(id int primary key not null, tags text[] not null, maker_date timestamp not null, value text)
In Bezug auf
distinct
Array-Werte nimmt das,tags
was enthält,a
20% Tabellenzeilenwhere 'a' = any(tags)
, b = 20%where 'b' = any(tags)
, c = 20%where 'c' = any(tags)
, d = 20%where 'd' = any(tags)
, e = 10%where 'e' = any(tags)
, f = 10%where 'f' = any(tags)
.Darüber hinaus
(tags, maker_date)
ist nicht eindeutig.Diese Tabelle ist nicht schreibgeschützt.
Es ist
sort on timestamp
, aber mein Beispiel zeigt Daten, tut mir leid.
Aktuelle Situation: tags = 'a' or tags = 'b' or tags = 'c'
und mehr
(1) Mit GIN index
oder Konvertieren text[] to int[]
sowie Konvertieren text[] to int
und mehr wird der Bitmap-Index für mehrere Tags verwendet. Schließlich entschied ich mich nach dem Testen, eine alte Lösung zu verwenden und OR
in viele UNION
Klauseln zu wechseln , die jeweils UNION
die Anzahl der Daten begrenzen. Natürlich werde ich partial index
für jeden Tag einen Wert erstellen, den ich mit (1) oben kombinieren kann. In Bezug auf OFFSET
wird WHERE
stattdessen eine oder mehrere Bedingungen in Klausel verwendet.
Beispiel
EXPLAIN (ANALYSE ON, costs ON, VERBOSE)
SELECT rs.*
FROM (
(SELECT tags,
id,
maker_date
FROM tags_tmp
WHERE 'a' = any(tags)
AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
ORDER BY maker_date DESC LIMIT 5)
UNION
(SELECT tags,
id,
maker_date
FROM tags_tmp
WHERE 'b' = any(tags)
AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
ORDER BY maker_date DESC LIMIT 5)
UNION
(SELECT tags,
id,
maker_date
FROM tags_tmp
WHERE 'c' = any(tags)
AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
ORDER BY maker_date DESC LIMIT 5)) rs
ORDER BY rs.maker_date DESC LIMIT 5 ;
quelle
a:2016-11-09
,b:2016-11-09
,c:2016-11-09
als Baumknoten und alle von ihnen Fügen Sie einen Zeiger auf die Zeile hinzu#1
. MongoDB unterstützt tatsächlich zusammengesetzte Multikey-Indizes ... PostgreSQL tut dies leider nicht, und das ist sehr ärgerlich. Sie müssten eine separate Tabelle mitid_ref | tag | date
erstellen, um einen ähnlichen B-Baum zu erstellen.Antworten:
Allgemeine Überlegungen
Index - Optimierung hängt immer von der kompletten Bild. Tabellengröße, Zeilengröße, Kardinalitäten, Wertehäufigkeiten, Selektivität typischer Abfragen, Postgres-Version, typische Zugriffsmuster usw.
Ihr Fall ist aus zwei Gründen besonders schwierig:
WHERE
undORDER BY
.Das Filtern nach Arrays ist mit dem GIN- oder GiST-Index am effizientesten, aber keiner der Indextypen erzeugt eine sortierte Ausgabe. Das Handbuch:
Sie können einen mehrspaltigen GIN-Index für
(tags, maker_date)
oder für mehrere Spalten erstellen (die Reihenfolge der Indexspalten ist für GIN-Indizes irrelevant). Sie müssen jedoch das zusätzliche Modulbtree_gin
installiert haben. Anleitung:Und es wird nicht für die
ORDER BY
Komponente Ihres Problems helfen .Noch eine Klarstellung: Ist
OFFSET m LIMIT n
in der Regel fast so teuer wieLIMIT m+n
.Lösung für zusätzliche Spezifikationen
Sie haben klargestellt: Nur 6 verschiedene Tags möglich. Das ist entscheidend.
Ihr Tisch ist groß und Ihre Tabellendefinition lässt Raum für Verbesserungen. Size matters für große Tabellen. Ihre Zahlen (30 GB, 10 Millionen Zeilen) deuten ebenfalls auf einen großen Durchschnitt hin. Zeilengröße von ~ 3 KB. Entweder haben Sie mehr Spalten als Sie anzeigen oder die Tabelle aufblähen und benötigen einen
VACUUM FULL
Lauf (oder ähnliches), oder Ihrevalue
Spalte ist groß und geröstet, was meine Verbesserungen noch effektiver machen würde, da die Hauptbeziehung damit auf die Hälfte ihrer Größe oder weniger reduziert wird ::Die Reihenfolge der Spalten ist aufgrund der Ausrichtungsauffüllung relevant. Einzelheiten:
Noch wichtiger ist :
tags int
. Warum?Arrays haben einen beträchtlichen Overhead von 24 Byte (ähnlich einer Zeile) plus tatsächliche Elemente.
Ein
text[]
mit 1-6 Elementen wie Sie demonstrieren ('lustig', 'Inspiration', ...) belegt also durchschnittlich ~ 56 Bytes . Und 6 verschiedene Werte können durch nur 6 Informationsbits dargestellt werden (vorausgesetzt, die Sortierreihenfolge des Arrays ist irrelevant). Wir könnten noch mehr komprimieren, aber ich habe den praktischeninteger
Typ gewählt (belegt 4 Bytes ), der Platz für bis zu 31 verschiedene Tags bietet. Dadurch bleibt Platz für spätere Ergänzungen ohne Änderungen am Tabellenschema. Detaillierte Begründung:Ihre Tags werden Bits in einer Bitmap zugeordnet,
'a'
wobei es sich um das niedrigstwertige Bit handelt (rechts):Das Tag-Array
'{a,d,f}'
ist also zugeordnet41
. Sie können beliebige Zeichenfolgen anstelle von 'a' - 'f' verwenden, spielt keine Rolle.Um die Logik zu kapseln, schlage ich zwei Hilfsfunktionen vor , die leicht erweiterbar sind:
Tags -> Ganzzahl:
Ganzzahl -> Tags:
Grundlagen hier:
Zur Vereinfachung können Sie eine Ansicht hinzufügen , um Tags wie gewünscht als Textarray anzuzeigen:
Jetzt kann Ihre grundlegende Abfrage sein:
Verwenden des binären UND-Operators
&
. Es gibt mehr Operatoren , um die Spalte zu bearbeiten.get_bit()
undset_bit()
von den binären Zeichenfolgenoperatoren sind auch bequem.Die obige Abfrage sollte bereits schneller sein, nur für die kleineren und billigeren Betreiber, aber noch nichts Revolutionäres. Um es schnell zu machen, brauchen wir Indizes, und die oben genannten können noch keinen Index verwenden.
Ein Teilindex für jedes Tag:
Diese Abfrage entspricht der obigen, kann jedoch die folgenden Indizes verwenden:
Postgres kann mehrere Bitmap-Index-Scans in einem
BitmapOr
Schritt sehr effizient kombinieren - wie in dieser SQL-Geige gezeigt .Sie können eine weitere Indexbedingung hinzufügen, um die Indizes auf
maker_date
einen konstanten Zeitstempel zu beschränken (und die wörtliche Bedingung in Abfragen wiederholen), um ihre Größe (massiv) zu verringern. Zugehöriges Beispiel:Anspruchsvoller:
Andere verwandte Antworten:
Wie kann die ORDER BY-Sortierung beschleunigt werden, wenn der GIN-Index in PostgreSQL verwendet wird?
Kann der räumliche Index bei einer Abfrage "Range-Order-By-Limit" helfen?
Oder nur 6
boolean
Spalten ...Einfach 6 boolesche Spalten könnten eine noch bessere Wahl sein. Beide Lösungen haben einige Vor- und Nachteile ...
Sie können die Flags
NOT NULL
abhängig von Ihrem vollständigen Anwendungsfall definieren.Erwägen:
Teilindizes einfach:
Usw.
Alternative für Ihren Sonderfall
Denken Sie noch etwas darüber nach, da all Ihre wenigen Tags so häufig sind und das Kombinieren mehrerer Tags mit OR noch weniger selektiv ist, ist es am schnellsten, nur einen btree-Index zu haben
maker_date DESC
. Postgres kann den Index durchlaufen und qualifizierende Zeilen nach Tags filtern. Dies funktioniert in Kombination mit separaten booleschen Spalten anstelle des Arrays oder der codierten Ganzzahl, da Postgres nützlichere Spaltenstatistiken für separate Spalten enthält.Und dann:
Paging
Sie benötigen eine eindeutige Sortierreihenfolge für Ergebnismengen, damit das Paging funktioniert. Ich habe mich nicht um diese Antwort gekümmert, sie ist schon zu lang. Normalerweise fügen Sie weitere Spalten hinzu
ORDER BY
. So funktioniert Paging effizient:quelle
Verschiedene Probleme mit Ihrem Testfall:
id
istint8
jetzt. Sie haben es wieint
in Ihrer ursprünglichen Frage erklärt. Kein großer Unterschied, aber warum die Verwirrung am Anfang? Dies ist wichtig für die Zeilengröße und die Ausrichtung. Bitte denken Sie daran, bei Fragen Ihre tatsächliche, genaue und vollständige Tabellendefinition anzugeben.Die Datenverteilung in Ihren Testdaten ist unrealistisch. Sie haben nur 6 verschiedene Kombinationen von Tags und * alle Zeilen haben Tags
'1'
. Ich gehe davon aus, dass Sie alle 63 möglichen Kombinationen in Ihrer Live-Tabelle haben, wobei die Tags so verteilt sind, wie Sie sie in der Frage hinzugefügt haben.Ihre Testtabelle enthält alte und neue Tag-Spalten, wodurch die Auswirkung auf die Speichergröße, nach der ich gesucht habe, negiert wird. Jetzt ist die Zeilengröße noch größer. Ihre Zeilengröße beträgt 124 - 164 Byte, in meinem Test nur 68 Byte (inkl. Auffüllung und Artikelkennung). Mehr als die doppelte Größe macht einen Unterschied.
Sie schreiben Größe = 4163 MB . Welche Größe?
Sie haben
order by random()
für die Testdaten. Ist Ihre produktive Tabelle wirklich so zufällig? In der Regel werden Daten grob nach Zeitstempel sortiert. Wie ist Ihre aktuelle Situation?Um zu sehen, welcher Plan ausgewählt wird, testen Sie
EXPLAIN
nur mit, um den Abfrageplan anzuzeigen, bevor Sie die Abfrage tatsächlich ausführen. Spart viel Zeit mit großen Tischen. Aber immer die Ausgabe vonEXPLAIN (ANALYZE, BUFFERS)
hier bereitstellen . In Ihrer Antwort (im Gegensatz zur Frage)cost=
fehlen Schätzungen. Das macht es schwierig, das Problem zu erraten.Aber keines dieser Probleme kann erklären, warum Sie einen sequentiellen Scan auch mit sehen
enable_seqscan = off
; Ein schneller Test auf meinem Laptop mit Postgres 9.5 funktioniert . Gleiches sollte für S. 9.6 gelten.Genau wie ich es bereits in der SQL Fiddle demonstriert habe .
Sind Sie sicher, dass Sie alle Indizes ordnungsgemäß erstellt haben?
quelle