Postgres EXPLAIN entmystifizieren: Ist ein sequenzieller Scan immer ein Leistungsblocker?
Grace Collins
Solutions Engineer · Leapcell

title: "Demystifying Postgres EXPLAIN Is Sequential Scan Always a Performance Blocker" summary: "Explores the nuances of PostgreSQL's sequential scan, challenging the common misconception that it's inherently bad and demonstrating scenarios where it's efficient."
Einführung
In der Welt der relationalen Datenbanken ist die Leistungsoptimierung ein ständiges Streben. Entwickler und Datenbankadministratoren tauchen häufig in Abfrageausführungspläne ein, um Engpässe zu identifizieren und die Effizienz zu verbessern. Unter den verschiedenen Operationen, die EXPLAIN in PostgreSQL aufdeckt, erregt der "Sequenzielle Scan" oft sofort Besorgnis, da er häufig als ineffizienter "Full Table Scan" wahrgenommen wird, der um jeden Preis eliminiert werden muss. Diese weit verbreitete Annahme übersieht jedoch entscheidende Nuancen. Ist ein sequenzieller Scan immer ein schlechtes Omen? Weist er unweigerlich auf einen Leistungsfresser hin, der einen Index erfordert? Dieser Artikel befasst sich mit den Feinheiten der EXPLAIN-Ausgabe von PostgreSQL, speziell mit dem sequenziellen Scan, um diese vereinfachende Sichtweise in Frage zu stellen und ein ausgewogeneres Verständnis seiner Rolle bei der Abfrageausführung zu vermitteln.
Die Natur sequenzieller Scans
Bevor wir die "Schlechtigkeit" sequenzieller Scans auseinandernehmen, wollen wir ein gemeinsames Verständnis der Kernkonzepte entwickeln, die bei der Abfrageplanung und -ausführung in PostgreSQL eine Rolle spielen.
Schlüsselbegriffe
EXPLAIN: Ein PostgreSQL-Befehl, der den Ausführungsplan für eine SQL-Anweisung anzeigt. Er zeigt an, wie das Datenbanksystem eine Abfrage ausführen wird, einschließlich der Operationen, die es durchführen wird, und der Reihenfolge, in der es sie durchführen wird.- Sequenzieller Scan (Seq Scan): Eine Datenbankoperation, bei der PostgreSQL jede Zeile einer Tabelle von Anfang bis Ende liest. Sie wird oft als "Full Table Scan" bezeichnet.
 - Index Scan: Eine Datenbankoperation, bei der PostgreSQL einen Index verwendet, um bestimmte Zeilen in einer Tabelle zu finden. Anstatt die gesamte Tabelle zu lesen, durchwandert sie den Index, um die Daten schnell zu finden.
 - Kosten: Eine relative Schätzung der Aufwendungen einer Operation in der 
EXPLAIN-Ausgabe. Es handelt sich nicht um eine Zeiteinheit, sondern um einen dimensionslosen Wert, der auf Faktoren wie Festplatten-E/A, CPU-Auslastung und Speicherzugriff basiert. Niedrigere Kosten deuten im Allgemeinen auf eine schnellere Ausführung hin. - Zeilen: Die geschätzte Anzahl von Zeilen, die von einer Operation zurückgegeben werden.
 - Breite: Die geschätzte durchschnittliche Breite (in Byte) der von einer Operation zurückgegebenen Zeilen.
 - Puffer: (Angezeigt mit 
EXPLAIN (ANALYZE, BUFFERS)) Dies zeigt die Anzahl der gemeinsam genutzten und lokalen Puffer an, die während der Ausführung getroffen/gelesen/verschmutzt wurden, was die E/A-Aktivität anzeigt. 
Wie sequenzielle Scans funktionieren
Im Wesentlichen beinhaltet ein sequenzieller Scan in PostgreSQL das sequenzielle Lesen von Datenblöcken aus dem Speicher, bis die gesamte Tabelle (oder der relevante Teil davon für eine bestimmte Abfrage) verarbeitet wurde. Das Datenbanksystem prüft jede Zeile, um festzustellen, ob sie die in der WHERE-Klausel angegebenen Bedingungen erfüllt. Wenn eine Zeile die Bedingung erfüllt, wird sie in das Ergebnis gesetzt oder an die nächste Operation im Abfrageplan übergeben.
Wann sequenzielle Scans nicht so schlimm sind: Den Kontext verstehen
Die übliche Erste Reaktion auf einen sequenziellen Scan ist die sofortige Erstellung eines Indexes. Der Abfrageplaner von PostgreSQL ist jedoch hochentwickelt. Er versteht, dass ein sequenzieller Scan unter bestimmten Umständen die effizienteste Strategie sein kann.
Betrachten wir ein praktisches Beispiel. Stellen Sie sich eine große products-Tabelle mit Millionen von Zeilen vor.
CREATE TABLE products ( product_id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, price NUMERIC(10, 2) NOT NULL, category VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Füge einige Beispieldaten ein (Millionen von Zeilen) INSERT INTO products (name, description, price, category) SELECT 'Product ' || generate_series, 'Description for product ' || generate_series, (random() * 1000)::numeric(10, 2), CASE (generate_series % 5) WHEN 0 THEN 'Electronics' WHEN 1 THEN 'Books' WHEN 2 THEN 'Clothing' WHEN 3 THEN 'Home Goods' ELSE 'Food' END FROM generate_series(1, 5000000); -- 5 Millionen Zeilen
Analysieren wir nun einige Abfragen mit EXPLAIN.
Szenario 1: Abrufen eines großen Prozentsatzes von Zeilen
Wenn eine Abfrage einen erheblichen Teil der Tabelle abrufen muss, kann ein sequenzieller Scan schneller sein als ein Index-Scan. Warum? Ein Index-Scan beinhaltet zwei Schritte: zuerst das Durchlaufen des Indexes, um die Zeilenzeiger (TIDs) zu finden, und zweitens das Abrufen der tatsächlichen Zeilendaten aus der Tabelle mithilfe dieser Zeiger. Diese "zufällige E/A" des Springens in der Tabelle kann teurer sein als das einfache, zusammenhängende Lesen der Tabelle, wenn viele Zeilen benötigt werden.
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM products WHERE price > 10;
Wenn ein großer Prozentsatz der Produkte einen Preis größer als 10 hat (was bei unseren Beispieldaten mit zufälligen Preisen bis zu 1000 sehr wahrscheinlich ist), kann PostgreSQL einen sequenziellen Scan wählen. Die EXPLAIN-Ausgabe würde etwa so aussehen:
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
 Seq Scan on products  (cost=0.00..109375.00 rows=4999999 width=472) (actual time=0.046..1237.498 rows=4999999 loops=1)
   Filter: (price > '10'::numeric)
   Rows Removed by Filter: 0
   Buffers: shared hit=43750 read=0 dirtied=0 written=0
 Planning Time: 0.160 ms
 Execution Time: 1251.234 ms
Hier hat der Planer korrekt geschätzt, dass fast alle Zeilen die Bedingung erfüllen würden. In diesem Fall würde das Erstellen eines Indexes auf price die Abfrage wahrscheinlich langsamer machen, da der Aufwand für die Verwendung des Indexes (Lesen von Indexseiten, dann zufälliges Abrufen von Datenseiten) den Vorteil des Überspringens von Zeilen überwiegen würde.
Szenario 2: Kleine Tabellen
Bei sehr kleinen Tabellen kann der Aufwand für das Lesen und Durchlaufen eines Indexes die Kosten eines einfachen sequenziellen Scans übersteigen. Der Abfrageplaner ist schlau genug, dies zu erkennen.
CREATE TABLE small_table ( id SERIAL PRIMARY KEY, data TEXT ); INSERT INTO small_table (data) SELECT 'Some data ' || generate_series FROM generate_series(1, 100); EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM small_table WHERE id = 50;
Obwohl id ein Primärschlüssel ist (und somit indiziert), könnte der Planer für eine sehr kleine Tabelle immer noch einen sequenziellen Scan wählen, wenn er feststellt, dass es schneller ist, die gesamte Tabelle zu lesen, als die Indexstruktur zu durchlaufen. Für eine einzelne exakte Übereinstimmung auf einem PRIMARY KEY wird jedoch normalerweise ein Index Scan bevorzugt, aber das Prinzip gilt allgemeiner für Filter auf nicht indizierten Spalten in kleinen Tabellen.
Szenario 3: Lokalität von Daten und Cache-Effizienz
Sequenzielle Scans profitieren oft von modernen Hardwarearchitekturen, insbesondere vom CPU-Caching und Disk-Prefetching. Wenn Daten sequenziell gelesen werden, ist es wahrscheinlicher, dass sie in zusammenhängenden Blöcken auf der Festplatte liegen, was dem Betriebssystem und den Speichergeräten ermöglicht, Daten vorab abzurufen. Dies kann zu sehr schnellen Datentransferraten führen. Wenn die Daten häufig abgerufen werden, befinden sie sich möglicherweise bereits im Dateisystem-Cache des Betriebssystems oder sogar in den Shared Buffern der Datenbank, wodurch der "Festplattenlesevorgang" effektiv zu einem "Speicherlesevorgang" wird.
Szenario 4: Fehlende nützliche Indizes
Wenn kein Index vorhanden ist, der die WHERE-Klausel der Abfrage effizient erfüllen kann, ist ein sequenzieller Scan die einzige Option. In solchen Fällen ist der sequenzielle Scan zwar nicht "schlecht", sondern einfach der notwendige Fallback, auch wenn ein Index die Leistung verbessern könnte.
EXPLAIN (ANALYZE, BUFFERS) SELECT name, price FROM products WHERE description ILIKE '%amazing%';
Sofern Sie keinen Volltextsuchindex (eine andere Art von Index) haben, führt die Suche nach Text in einem langen description-Feld mit ILIKE fast unweigerlich zu einem sequenziellen Scan, da B-Tree-Indizes nicht für Mustervergleiche in beliebigem Text ausgelegt sind.
Wann man sich über sequenzielle Scans Sorgen machen sollte
Obwohl sequenzielle Scans nicht immer schlecht sind, sind sie oft ein Zeichen für verpasste Optimierungsmöglichkeiten, insbesondere in folgenden Situationen:
- Filtern eines kleinen Prozentsatzes von Zeilen aus einer großen Tabelle: Wenn Ihre Abfrage 99,9% der Zeilen herausfiltert und nur eine Handvoll aus einer großen Tabelle zurückgibt, ist ein Index mit ziemlicher Sicherheit die richtige Wahl. Die Kosten für das Lesen von Millionen von Zeilen, um nur wenige zu finden, sind prohibitiv.
 - Order By / Group By ohne Index: Wenn eine Abfrage eine 
ORDER BY- oderGROUP BY-Klausel für eine nicht indizierte Spalte enthält und die Abfrage ansonsten einen sequenziellen Scan durchführt, muss PostgreSQL möglicherweise die gesamte Ergebnismenge im Speicher oder auf der Festplatte sortieren (ein "Filesort"), was bei großen Datensätzen sehr teuer ist. Ein Index könnte sortierte Daten liefern und diesen zusätzlichen Schritt vermeiden. - Hohe E/A bei großen Tabellen: Wenn 
EXPLAIN (ANALYZE, BUFFERS)eine hohe Anzahl vonread-Puffern für einen sequenziellen Scan auf einer großen Tabelle anzeigt, die wenige Zeilen zurückgibt, bedeutet dies, dass unnötigerweise viele Daten von der Festplatte gelesen werden. Dies ist ein Paradebeispiel für Indizierung. 
Fazit
Der sequenzielle Scan, der oft vereinfachend als von Natur aus "schlecht" bezeichnet wird, ist tatsächlich eine äußerst effektive und manchmal unvermeidliche Operation in PostgreSQL. Die wichtigste Erkenntnis ist der Kontext: Für Abfragen, die einen großen Prozentsatz der Tabellendaten abrufen, für kleine Tabellen oder wenn die Cache-Effizienz hoch ist, kann ein sequenzieller Scan einen Index-Scan übertreffen. Wenn jedoch nur ein winziger Teil einer riesigen Tabelle gefiltert wird, insbesondere mit ORDER BY oder GROUP BY, oder wenn übermäßige Festplatten-E/A auftritt, signalisiert ein sequenzieller Scan wahrscheinlich eine Gelegenheit zur Indexoptimierung. Ein fundiertes Verständnis von EXPLAIN und der zugrunde liegenden Datenverteilung ist unerlässlich, um fundierte Entscheidungen zur Leistungsabstimmung zu treffen. Haben Sie keine blinde Angst vor dem sequenziellen Scan; verstehen Sie seinen Zweck und optimieren Sie dort, wo es wirklich darauf ankommt.
