Was wird während einer Abfrage von der Festplatte abgerufen?


13

Ziemlich einfache Frage, wahrscheinlich irgendwo beantwortet, aber ich kann scheinbar nicht die richtige Suchfrage für Google bilden ...

Beeinflusst die Anzahl der Spalten in einer bestimmten Tabelle die Leistung einer Abfrage, wenn eine Teilmenge dieser Tabelle abgefragt wird?

Wenn beispielsweise die Tabelle Foo 20 Spalten enthält, meine Abfrage jedoch nur 5 dieser Spalten auswählt, wirken sich dann 20 Spalten (gegenüber beispielsweise 10 Spalten) auf die Abfrageleistung aus? Der Einfachheit halber sei angenommen, dass irgendetwas in der WHERE-Klausel in diesen 5 Spalten enthalten ist.

Ich mache mir Sorgen über die Verwendung des Puffercaches von Postgres zusätzlich zum Festplattencache des Betriebssystems. Ich habe ein sehr schlechtes Verständnis für das physische Speicherdesign von Postgres. Tabellen werden auf mehreren Seiten gespeichert (standardmäßig 8 KB pro Seite), aber ich verstehe nicht ganz, wie Tupel von dort angeordnet sind. Ist PG intelligent genug, um nur die Daten von der Festplatte abzurufen, die diese 5 Spalten umfassen?


Sie sprechen über das Abrufen von 50 Bytes, aber nicht der verbleibenden 150. Ihre Festplatte liest wahrscheinlich in größeren Schritten als das!
Andomar

Woher bekommen Sie diese Nummern?
Jmoney38

Antworten:


14

Der physische Speicher für Zeilen ist in den Dokumenten unter Datenbankseitenlayout beschrieben . Der Spalteninhalt für dieselbe Zeile wird alle auf derselben Datenträgerseite gespeichert, mit Ausnahme des Inhalts von TOAST (zu groß, um auf eine Seite zu passen). Der Inhalt wird nacheinander in jeder Zeile extrahiert, wie nachfolgend erläutert:

Um die Daten zu lesen, müssen Sie jedes Attribut der Reihe nach untersuchen. Überprüfen Sie zunächst, ob das Feld gemäß der Null-Bitmap NULL ist. Wenn ja, fahren Sie mit dem nächsten fort. Stellen Sie dann sicher, dass Sie die richtige Ausrichtung haben. Wenn das Feld eine feste Breite hat, werden alle Bytes einfach platziert.

Im einfachsten Fall (keine TOAST-Spalten) ruft postgres die gesamte Zeile ab, auch wenn nur wenige Spalten benötigt werden. In diesem Fall lautet die Antwort also "Ja". Wenn mehr Spalten vorhanden sind, kann sich dies eindeutig nachteilig auf den Puffercache auswirken, insbesondere wenn der Spalteninhalt groß ist und sich noch unter dem TOAST-Schwellenwert befindet.

Nun der TOAST-Fall: Wenn ein einzelnes Feld ~ 2 KB überschreitet, speichert die Engine den Feldinhalt in einer separaten physischen Tabelle. Es kommt auch ins Spiel, wenn die gesamte Zeile nicht auf eine Seite passt (standardmäßig 8 KB): Einige der Felder werden in den TOAST-Speicher verschoben. Doc sagt:

Wenn es sich um ein Feld mit variabler Länge handelt (attlen = -1), ist es etwas komplizierter. Alle Datentypen variabler Länge haben die gemeinsame Strukturvariable für die Header-Struktur gemeinsam, die die Gesamtlänge des gespeicherten Werts und einige Flag-Bits enthält. Abhängig von den Flags können die Daten entweder inline oder in einer TOAST-Tabelle sein. es könnte auch komprimiert sein

TOAST'ed-Inhalte werden nicht abgerufen, wenn sie nicht ausdrücklich benötigt werden. Daher ist ihre Auswirkung auf die Gesamtzahl der abzurufenden Seiten gering (einige Bytes pro Spalte). Dies erklärt die Ergebnisse in der Antwort von @ dezso.

Wie beim Schreiben wird jede Zeile mit all ihren Spalten bei jedem UPDATE vollständig neu geschrieben, unabhängig davon, welche Spalten geändert werden. Wenn Sie also mehr Spalten haben, ist dies für Schreibvorgänge offensichtlich teurer.


Das ist eine klasse Antwort. Genau das, wonach ich suche. Vielen Dank.
Jmoney38

1
Eine gute Ressource, die ich in Bezug auf die Zeilenstruktur (Seitenüberprüfung und einige Verwendungsbeispiele) hier gefunden habe .
Jmoney38

9

Daniels Antwort konzentriert sich auf die Kosten für das Lesen einzelner Zeilen. In diesem Zusammenhang NOT NULLhilft es ein wenig, Spalten mit fester Größe an erster Stelle in Ihre Tabelle zu setzen. Es hilft ein wenig, die relevanten Spalten (die, nach denen Sie fragen) an erster Stelle zu setzen. Das Minimieren des Auffüllens (aufgrund der Datenausrichtung) durch Spielen von Ausrichtungstetris mit Ihren Spalten kann ein wenig helfen. Der wichtigste Effekt wurde jedoch noch nicht erwähnt, insbesondere bei großen Tischen.

Zusätzliche Spalten sorgen offensichtlich dafür, dass eine Zeile mehr Speicherplatz abdeckt, sodass weniger Zeilen auf eine Datenseite passen (standardmäßig 8 KB). Einzelne Zeilen sind auf mehrere Seiten verteilt. Das Datenbankmodul muss im Allgemeinen ganze Seiten abrufen, nicht einzelne Zeilen . Es spielt keine Rolle, ob einzelne Zeilen etwas kleiner oder größer sind - solange die gleiche Anzahl von Seiten gelesen werden muss.

Wenn eine Abfrage einen (relativ) kleinen Teil einer großen Tabelle abruft, bei dem die Zeilen mehr oder weniger zufällig über die gesamte Tabelle verteilt sind, was durch einen Index unterstützt wird, führt dies zu ungefähr der gleichen Anzahl von Seitenlesevorgängen, ohne dass dies berücksichtigt wird zu Zeilengröße. Irrelevante Spalten verlangsamen Sie in einem solchen (seltenen) Fall nicht sehr.

In der Regel rufen Sie Patches oder Cluster von Zeilen ab, die nacheinander oder in der Nähe eingegeben wurden, und geben Datenseiten frei. Diese Zeilen sind aufgrund der Unordnung verteilt. Es müssen mehr Plattenseiten gelesen werden, um Ihre Abfrage zu erfüllen. Das Lesen weiterer Seiten ist in der Regel der wichtigste Grund für eine langsamere Abfrage. Und das ist der wichtigste Faktor, warum irrelevante Spalten Ihre Abfragen langsamer machen.

Bei großen Datenbanken ist in der Regel nicht genügend RAM vorhanden, um den gesamten Arbeitsspeicher im Cache zu halten. Größere Zeilen belegen mehr Cache, mehr Konflikte, weniger Cache-Treffer und mehr Festplatten-E / A. Und Festplattenlesevorgänge sind in der Regel viel teurer. Weniger bei SSDs, aber ein wesentlicher Unterschied bleibt. Dies fügt dem obigen Punkt über Seitenlesevorgänge hinzu.

Es kann von Bedeutung sein, ob irrelevante Spalten TOAST-ed sind. Relevante Spalten können auch TOAST-ed sein, wodurch der gleiche Effekt erzielt wird.


1

Ein kleiner Test:

CREATE TABLE test2 (
    id serial PRIMARY KEY,
    num integer,
    short_text varchar(32),
    longer_text varchar(1000),
    long_long_text text
);

INSERT INTO test2 (num, short_text, longer_text, long_long_text)
SELECT i, lpad('', 32, 'abcdefeghji'), lpad('', 1000, 'abcdefeghji'), lpad('', (random() * 10000)::integer, 'abcdefeghji')
FROM generate_series(1, 10000) a(i);

ANALYZE test2;

SELECT * FROM test2;
[...]
Time: 1091.331 ms

SELECT num FROM test2;
[...]
Time: 21.310 ms

Das Begrenzen der Abfrage auf die ersten 250 Zeilen ( WHERE num <= 250) führt zu 34.539 ms bzw. 8.343 ms. Die Auswahl aller außer long_long_textdieser begrenzten Menge führt zu 18.432 ms. Dies zeigt, dass PG in Ihren Begriffen klug genug ist.


Nun, ich weiß den Input zu schätzen. Ich kann jedoch nicht mit Sicherheit sagen, dass dieses Testszenario beweist, was ich ursprünglich vorgeschlagen habe. Es gibt einige Probleme. Zum einen, wenn Sie zum ersten Mal "SELECT * FROM test2" ausführen, sollte dies Ihren gemeinsam genutzten Puffercache gefüllt haben. Das Abrufen dieser Abfrage von der Festplatte hätte viel länger gedauert. Somit wäre die zweite Abfrage theoretisch viel schneller gewesen, weil sie aus dem SB-Cache abgerufen worden wäre. Aber ich bin damit einverstanden, dass PG nur die Zeilen abruft, die es benötigt, basierend auf Ihren späteren Tests / Vergleichen.
Jmoney38

Sie haben recht, dieser Test (der einfach ist) hat seine Mängel. Wenn ich genug Zeit habe, werde ich versuchen, diese auch abzudecken.
Dezso
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.