Index, der nicht verwendet wird, aber die Abfrage beeinflusst

8

Ich habe eine PostgreSQL 9.3-Tabelle mit einigen Zahlen und einigen zusätzlichen Daten:

CREATE TABLE mytable (
    myid BIGINT,
    somedata BYTEA
)

Diese Tabelle enthält derzeit ca. 10 Millionen Datensätze und benötigt 1 GB Speicherplatz. myidsind nicht aufeinanderfolgend.

Ich möchte berechnen, wie viele Zeilen sich in jedem Block mit 100000 aufeinander folgenden Zahlen befinden:

SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;

Dies gibt ungefähr 3500 Zeilen zurück.

Ich habe festgestellt, dass das Vorhandensein eines bestimmten Index diese Abfrage erheblich beschleunigt, obwohl der Abfrageplan dies überhaupt nicht erwähnt. Der Abfrageplan ohne Index:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=1636639.92..1709958.65 rows=496942 width=8) (actual time=6783.763..8888.841 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Sort  (cost=1636639.92..1659008.91 rows=8947594 width=8) (actual time=6783.752..8005.831 rows=8947557 loops=1)
         Output: ((myid / 100000))
         Sort Key: ((mytable.myid / 100000))
         Sort Method: external merge  Disk: 157440kB
         ->  Seq Scan on public.mytable  (cost=0.00..236506.92 rows=8947594 width=8) (actual time=0.020..1674.838 rows=8947557 loops=1)
               Output: (myid / 100000)
 Total runtime: 8914.780 ms
(9 rows)

Der Index:

db=> CREATE INDEX myindex ON mytable ((myid/100000));
db=> VACUUM ANALYZE;

Der neue Abfrageplan:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=281242.99..281285.97 rows=3439 width=8) (actual time=3190.189..3190.800 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Seq Scan on public.mytable  (cost=0.00..236505.56 rows=8947485 width=8) (actual time=0.026..1659.571 rows=8947557 loops=1)
         Output: (myid / 100000)
 Total runtime: 3190.975 ms
(5 rows)

Die Abfragepläne und die Laufzeiten unterscheiden sich also erheblich (fast dreimal), erwähnen jedoch weder den Index. Dieses Verhalten ist auf meinem Entwicklungscomputer perfekt reproduzierbar: Ich habe mehrere Zyklen durchlaufen, in denen der Index gelöscht, die Abfrage mehrmals getestet, der Index neu erstellt und die Abfrage erneut mehrmals getestet wurde. Was passiert hier?

liori
quelle
Ich bin kein Experte für die Analyse der Abfragepläne von Postgres, aber ich denke, der Index wird für die HashAggregateMethode verwendet (und es ist keine Sortierung erforderlich), damit Sie eine bessere Leistung erzielen. Warum der Index im Plan nicht erwähnt wird, weiß ich nicht.
Ypercubeᵀᴹ
Ändert sich die Ausgabe des Plans, wenn Sie den ausführlichen Modus mit: aktivieren explain (analyze true, verbose true) ...?
a_horse_with_no_name
Es wäre großartig, wenn Sie diesen in einem eigenständigen Testfall zusammenfassen könnten. Es scheint sicher seltsam.
Craig Ringer
@a_horse_with_no_name: Ja, es ändert sich - ich habe die Abfragepläne durch die ausführlichen in der Frage ersetzt. In diesem Abfrageplan wird der Index jedoch immer noch überhaupt nicht erwähnt.
Liori
Wenn in der ID-Spalte mit dem Index mehr Statistiken verfügbar sind (insbesondere Kardinalität und möglicherweise Min / Max-Werte) als ohne, kann dies die Gruppe des Optimierers nach Methodenauswahl ändern, auch wenn der Index überhaupt nicht verwendet wird . (Ich kenne Postgres 'Optimierer und Statistiken überhaupt nicht, also keine Ahnung, ob das der Fall sein könnte oder nicht.)
Mat

Antworten:

3

VACUUM ANALYZEmacht den Unterschied in Ihrem Beispiel. Außerdem, wie von @jjanes angegeben , die zusätzlichen Statistiken für den Funktionsindex. Pro Dokumentation:

pg_statisticspeichert auch statistische Daten über die Werte von Indexausdrücken. Diese werden so beschrieben, als wären sie tatsächliche Datenspalten. starelidverweist insbesondere auf den Index. Für eine normale Indexspalte ohne Ausdruck wird jedoch kein Eintrag vorgenommen, da sie mit dem Eintrag für die zugrunde liegende Tabellenspalte redundant wäre.

Das Erstellen des Index führt jedoch nicht dazu, dass Postgres Statistiken sammelt. Versuchen:

CREATE INDEX myindex ON mytable ((myid/100000));
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Gibt nichts zurück, bis Sie Ihren ersten ausführen ANALYZE(oder VACUUM ANALYZEoder der Autovacuum-Daemon startet).

ANALYZE mytable;
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Jetzt sehen Sie hinzugefügte Statistiken.

Da die gesamte Tabelle ohnehin gelesen werden muss, wird Postgres einen sequentiellen Scan verwenden, es sei denn, es wird erwartet, dass die Berechnung für myid/100000den Wechsel teuer genug ist, was nicht der Fall ist.

Ihre einzige andere Chance wäre ein Nur-Index-Scan, wenn der Index viel kleiner als die Tabelle ist - und die Voraussetzungen für einen Nur-Index-Scan erfüllt sind. Details im Postgres Wiki und im Handbuch .

Solange dieser Funktionsindex nicht verwendet wird, ist der Nutzen der Sicherheiten durch zusätzliche Statistiken moderat. Wenn die Tabelle schreibgeschützt wäre, wären die Kosten niedrig - aber andererseits würden wir wahrscheinlich sofort einen Nur-Index-Scan sehen.

Vielleicht können Sie auch bessere Abfragepläne erzielen, indem Sie ein höheres Statistikziel für festlegen mytable.myid. Das würde nur geringe Kosten verursachen. Mehr:

Erwin Brandstetter
quelle
Vielen Dank für diese Erklärung, sie ist sehr hilfreich, um das Problem zu verstehen. In meinem Fall benötige ich höchstwahrscheinlich eine zusätzliche myid/100000 BETWEEN somevalue AND othervalueBedingung, sodass der Index ohnehin im Abfrageplan verwendet wird. Ich habe diese Frage gerade gestellt, weil ich nicht verstanden habe, warum der Index im Fall der gesamten Tabelle nützlich ist.
Liori
@liori: Sie könnten decken , dass mit WHERE myid BETWEEN somevalue*100000 AND othervalue*100000(man denke Rundungseffekte auf Ihre Typen abhängig), und Sie haben wahrscheinlich bereits einen einfachen Index auf myid, so dass Sie ohne zusätzliche Spezialindex tun können. Könnte effizienter sein.
Erwin Brandstetter
6

Wenn Sie einen Ausdrucksindex erstellen, sammelt PostgreSQL Statistiken zu diesem Ausdruck. Mit diesen Statistiken verfügt es nun über eine genaue Schätzung der Anzahl der aggregierten Zeilen, die von der Abfrage zurückgegeben werden, was dazu führt, dass eine bessere Planauswahl getroffen wird.

Insbesondere in diesem Fall wurde die Hash-Tabelle ohne diese zusätzlichen Statistiken für zu groß gehalten, um in work_mem zu passen, sodass diese Methode nicht ausgewählt wurde.

jjanes
quelle
Ich denke, der Planer berücksichtigt den Wert von nicht work_mem. Wenn Sie es so erhöht haben, dass die Sortierung in den Speicher passt, würde es immer noch denselben Plan verwenden. Lassen Sie mich merken hier , dass die Zeitdifferenz ( die meisten davon) von der externen Festplatte sortieren kommt.
Dekso
1
@dezso Was ist, wenn Sie den Wert von work_mem, der benötigt wurde, um die Sortierung in den Speicher zu passen, experimentell verdoppeln oder verdreifachen? Sortieren und Hashing haben unterschiedliche Overhead-Schätzungen, und die Schätzungen selbst sind nicht sehr genau. Welche Nebenversion von 9.3 verwenden Sie?
jjanes