Rezepte für fehlerhafte SQL-Abfragen

Vor einigen Monaten haben wir angekündigt explain.tensor.ru — einen öffentlichen Dienst zur Analyse und Visualisierung von Abfrageplänen für PostgreSQL.

In der Zwischenzeit haben Sie ihn bereits über 6000 Mal genutzt, aber eine der nützlichen Funktionen könnte unbemerkt geblieben sein — das sind strukturelle Hinweise, die ungefähr so aussehen:

Rezepte für fehlerhafte SQL-Abfragen

Achten Sie darauf, denn Ihre Abfragen werden dadurch "glatt und geschmeidig". 🙂

Im Ernst, viele Situationen, die eine Abfrage langsam und "ressourcenhungrig" machen, sind typisch und können anhand der Struktur und der Daten des Plans erkannt werden..

In diesem Fall muss jeder einzelne Entwickler nicht selbst nach einer Optimierungsmöglichkeit suchen und sich nur auf seine Erfahrung stützen — wir können ihm sagen, was hier passiert, worin das Problem liegen könnte und wie er an die Lösung herangehen kann.Das haben wir auch getan.

Rezepte für fehlerhafte SQL-Abfragen

Lassen Sie uns die Fälle etwas genauer betrachten — wie sie identifiziert werden und zu welchen Empfehlungen sie führen.

Um tiefer in das Thema einzutauchen, können Sie zunächst einen entsprechenden Abschnitt aus meinem Vortrag auf der PGConf.Russia 2020anhören, bevor wir zu einer detaillierten Analyse jedes Beispiels übergehen:

Video abspielen

#1: индексная «недосортировка»

Wann tritt dies auf?

Letzte Rechnung des Kunden „ООО Колокольчик“ anzeigen.

Wie man erkennt

-> Limit
   -> Sortieren
      -> Index [Nur] Rückwärts-Scan | Bitmap Heap Scan

Empfehlungen

Verwendeter Index mit Sortierfeldern erweitern.

Beispiel:

CREATE TABLE tbl AS
SELECT
  generate_series(1, 100000) pk  -- 100K "Fakten"
, (random() * 1000)::integer fk_cli; -- 1K verschiedene Fremdschlüssel

CREATE INDEX ON tbl(fk_cli); -- Index für den Fremdschlüssel

SELECT
  *
FROM
  tbl
WHERE
  fk_cli = 1 -- Auswahl nach spezifischer Beziehung
ORDER BY
  pk DESC -- wir möchten nur einen "letzten" Eintrag
LIMIT 1;

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Es ist sofort erkennbar, dass über den Index mehr als 100 Einträge ausgelesen wurden, die dann alle sortiert und anschließend nur einer behalten wurde.

Wir korrigieren:

DROP INDEX tbl_fk_cli_idx;
CREATE INDEX ON tbl(fk_cli, pk DESC); -- Sortierschlüssel hinzugefügt

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Selbst bei dieser primitiven Auswahl — 8,5 Mal schneller und 33 Mal weniger Lesevorgänge.. Der Effekt wird umso deutlicher, je mehr „Fakten“ Sie für jeden Wert haben. fk.

Ich möchte darauf hinweisen, dass dieser Index nicht schlechter als der frühere als „präfixbasiert“ auch bei anderen Abfragen funktioniert, fk, wo es keine Sortierungen nach pk gab und gibt (mehr dazu können Sie lesen in meinem Artikel über die Suche nach ineffizienten Indizes). Er wird auch die ordnungsgemäße Unterstützung des expliziten Fremdschlüssels für dieses Feld gewährleisten.

#2: пересечение индексов (BitmapAnd)

Wann tritt dies auf?

Alle Verträge für den Kunden „OOO Kolokolchik“, die im Namen von „NAO Lyutik“ abgeschlossen wurden, anzeigen.

Wie man erkennt

-> BitmapAnd
   -> Bitmap Index Scan
   -> Bitmap Index Scan

Empfehlungen

Erstellen Sie komplexer Index Über die Felder beider Tabellen oder einen der bestehenden Felder aus der zweiten Tabelle erweitern.

Beispiel:

CREATE TABLE tbl AS
SELECT
  generate_series(1, 100000) pk      -- 100K "Fakten"
, (random() *  100)::integer fk_org  -- 100 verschiedene Fremdschlüssel
, (random() * 1000)::integer fk_cli; -- 1K verschiedene Fremdschlüssel

CREATE INDEX ON tbl(fk_org); -- Index für den Fremdschlüssel
CREATE INDEX ON tbl(fk_cli); -- Index für den Fremdschlüssel

SELECT
  *
FROM
  tbl
WHERE
  (fk_org, fk_cli) = (1, 999); -- Auswahl nach spezifischem Paar

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Wir korrigieren:

DROP INDEX tbl_fk_org_idx;
CREATE INDEX ON tbl(fk_org, fk_cli);

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Hier ist der Gewinn geringer, da der Bitmap Heap Scan an sich bereits effizient ist. Dennoch sieben Mal schneller und mit 2,5 Mal weniger Lesevorgängen.

#3: объединение индексов (BitmapOr)

Wann tritt dies auf?

Die ersten 20 ältesten „eigenen“ oder nicht zugewiesenen Anträge zur Bearbeitung anzeigen, wobei die eigenen Vorrang haben.

Wie man erkennt

-> BitmapOr
   -> Bitmap Index Scan
   -> Bitmap Index Scan

Empfehlungen

Verwenden UNION [ALL] zum Zusammenführen von Unterabfragen für jeden der OR-Bedingungen.

Beispiel:

ERSTELLEN SIE EINE TABELLE tbl ALS
SELECT
  generate_series(1, 100000) pk  -- 100K "Fakten"
, FALLS
    random() < 1::real/16 THEN NULL -- mit einer Wahrscheinlichkeit von 1:16 ist der Eintrag "Unentschieden"
    SONST (random() * 100)::integer -- 100 verschiedene Fremdschlüssel
  ENDE fk_own;

EINDEDEXTERN AUF tbl(fk_own, pk); -- Index mit "anscheinend passender" Sortierung

SELECT
  *
FROM
  tbl
WHERE
  fk_own = 1 ODER -- eigene
  fk_own IST NULL -- ... oder "Unentschieden"
ORDER BY
  pk
, (fk_own = 1) DESC -- zuerst "eigene"
LIMIT 20;

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Wir korrigieren:

(
  SELECT
    *
  FROM
    tbl
  WHERE
    fk_own = 1 -- zuerst "eigene" 20
  ORDER BY
    pk
  LIMIT 20
)
UNION ALL
(
  SELECT
    *
  FROM
    tbl
  WHERE
    fk_own IST NULL -- dann "Unentschieden" 20
  ORDER BY
    pk
  LIMIT 20
)
LIMIT 20; -- aber insgesamt - 20, mehr brauchen wir nicht

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Wir haben genutzt, dass alle 20 benötigten Datensätze sofort im ersten Block erhalten wurden, daher wurde der zweite, mit dem "teurerem" Bitmap Heap Scan, nicht einmal ausgeführt — letztendlich 22 Mal schneller, bei 44 Mal weniger Lesevorgängen!

Eine detaillierte Erzählung über diese Optimierungsmethode an konkreten Beispielen kann in den Artikeln gelesen werden PostgreSQL Antipatterns: schädliche JOINs und OR und PostgreSQL Antipatterns: eine Geschichte über die iterative Verbesserung der Suche nach Namen, oder "Optimierung hin und zurück".

Eine verallgemeinerte Version der geordneten Auswahl nach mehreren Schlüsseln (und nicht nur nach einem Paar const/NULL) wird in dem Artikel behandelt SQL HowTo: Schreiben Sie eine while-Schleife direkt in die Abfrage, oder „Elementare Drei-Wege-Abfrage“.

#4: читаем много лишнего

Wann tritt dies auf?

In der Regel tritt es auf, wenn der Wunsch besteht, "ein weiteres Filterkriterium" an die bereits bestehende Abfrage anzuhängen.

"Haben Sie nicht so etwas ähnliches, aber mit perlweissen KnöpfenFilm „Die diamantene Hand“

Zum Beispiel, indem wir die oben genannte Aufgabe modifizieren, zeigen Sie die ersten 20 ältesten „kritischen“ Anträge zur Bearbeitung, unabhängig von ihrer Zuordnung.

Wie man erkennt

-> Seq Scan | Bitmap Heap Scan | Index [Only] Scan [Backward]
   && 5 × Zeilen < RRbF -- mehr als 80 % des Gelesenen gefiltert
   && Schleifen × RRbF > 100 -- und dabei insgesamt mehr als 100 Einträge

Empfehlungen

Erstellen Sie [eine] spezialisierte Index mit einer WHERE-Bedingung oder fügen Sie weitere Felder in den Index ein.

Wenn die Filterbedingung „statisch“ für Ihre Aufgaben ist – das heißt, keine Erweiterung der Werte-Liste in der Zukunft vorsieht – ist es besser, einen WHERE-Index zu verwenden. In diese Kategorie fallen gut verschiedene boolean/enum-Status.

Wenn jedoch die Filterbedingung verschiedene Werte annehmen kann, ist es besser, den Index um diese Felder zu erweitern – wie im Fall von BitmapAnd oben.

Beispiel:

CREATE TABLE tbl AS
SELECT
  generate_series(1, 100000) pk -- 100K "Fakten"
, CASE
    WHEN random() < 1::real/16 THEN NULL
    ELSE (random() * 100)::integer -- 100 verschiedene Fremdschlüssel
  END fk_own
, (random() < 1::real/50) kritisch; -- 1:50, dass der Antrag "kritisch" ist

CREATE INDEX ON tbl(pk);
CREATE INDEX ON tbl(fk_own, pk);

SELECT
  *
FROM
  tbl
WHERE
  kritisch
ORDER BY
  pk
LIMIT 20;

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Wir korrigieren:

CREATE INDEX ON tbl(pk)
  WHERE kritisch; -- hinzugefügt "statische" Filterbedingung

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Wie wir sehen, ist die Filterung aus dem Plan vollständig weggefallen, und die Anfrage wurde fünf Mal schneller.

#5: разреженная таблица

Wann tritt dies auf?

Verschiedene Versuche, eine eigene Warteschlange zur Aufgabenbearbeitung zu erstellen, wenn eine große Anzahl von Aktualisierungen/Löschungen in der Tabelle zu einer Situation mit vielen "toten" Datensätzen führt.

Wie man erkennt

-> Seq Scan | Bitmap Heap Scan | Index [Only] Scan [Backward]
   && Schleifen × (Zeilen + RRbF)  64

Empfehlungen

Regelmäßig manuell durchführen VACUUM [FULL] oder eine angemessen häufige Ausführung erreichen autovacuum durch Feinabstimmung seiner Parameter, einschließlich für eine bestimmte Tabelle.

In den meisten Fällen werden solche Probleme durch eine schlechte Anordnung der Anfragen bei Aufrufen aus der Geschäftslogik, wie die, die in PostgreSQL-Antipatterns: Kämpfen gegen die Horden der "Untoten".

Aber man muss verstehen, dass selbst VACUUM FULL nicht immer helfen kann. In solchen Fällen sollte man sich mit dem Algorithmus aus dem Artikel vertraut machen DBA: wenn VACUUM scheitert – reinigen wir die Tabelle manuell.

#6: чтение с «середины» индекса

Wann tritt dies auf?

Es scheint, als hätten wir ein wenig gelesen, und alles läuft über den Index, und wir haben nichts überflüssiges gefiltert – trotzdem wurden deutlich mehr Seiten gelesen, als gewünscht.

Wie man erkennt

-> Index [Only] Scan [Backward]
   && Schleifen × (Zeilen + RRbF)  64

Empfehlungen

Achten Sie genau auf die Struktur des verwendeten Index und die Schlüssel, die in der Abfrage angegeben sind – sehr wahrscheinlich, ist ein Teil des Index nicht definiert. Möglicherweise müssen Sie einen ähnlichen Index erstellen, jedoch ohne Präfixfelder oder lernen, ihre Werte zu iterieren.

Beispiel:

CREATE TABLE tbl AS
SELECT
  generate_series(1, 100000) pk      -- 100K "Fakten"
, (random() *  100)::integer fk_org  -- 100 verschiedene Fremdschlüssel
, (random() * 1000)::integer fk_cli; -- 1K verschiedene Fremdschlüssel

CREATE INDEX ON tbl(fk_org, fk_cli); -- alles fast wie in #2
-- nur haben wir den separaten Index für fk_cli als überflüssig erachtet und entfernt

SELECT
  *
FROM
  tbl
WHERE
  fk_cli = 999 -- fk_org ist nicht definiert, obwohl früher im Index angegeben
LIMIT 20;

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Es scheint alles in Ordnung zu sein, sogar nach dem Index, aber irgendwie verdächtig – für jeden der 20 gelesenen Datensätze mussten 4 Seiten Daten gelesen werden, 32KB pro Datensatz – ist das nicht ein bisschen viel? Und der Name des Indexes tbl_fk_org_fk_cli_idx regt zum Nachdenken an.

Wir korrigieren:

CREATE INDEX ON tbl(fk_cli);

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Plötzlich – 10-mal schneller, und 4-mal weniger zu lesen!

Weitere Beispiele für ineffiziente Nutzung von Indizes finden Sie im Artikel DBA: Wir identifizieren unnötige Indizes.

#7: CTE × CTE

Wann tritt dies auf?

In der Abfrage haben wir 'fette' CTE aus verschiedenen Tabellen erfasst und danach entschieden, zwischen ihnen JOIN.

Der Fall ist relevant für Versionen unter v12 oder Abfragen mit WITH MATERIALIZED.

Wie man erkennt

-> CTE Scan
   && Schleifen > 10
   && Schleifen × (Zeilen + RRbF) > 10000
      -- zu großes kartesisches Produkt von CTE

Empfehlungen

Die Abfrage sorgfältig analysieren — ob CTEs hier überhaupt notwendig sind? Если все-таки да, то ''Wortschatz'' in hstore/json anwenden nach dem Modell, das in PostgreSQL Antipatterns: wir schlagen mit dem Wörterbuch auf bei schweren JOINs..

#8: swap на диск (temp written)

Wann tritt dies auf?

Die einmalige Bearbeitung (Sortierung oder Einzigartigkeit) einer großen Menge von Datensätzen passt nicht in den dafür reservierten Speicher.

Wie man erkennt

-> *
   && temp geschrieben > 0

Empfehlungen

Wenn der von der Operation verwendete Speicher die festgelegte Wertgrenze des Parameters work_memnicht erheblich überschreitet, sollte er angepasst werden. Dies kann entweder global in der Konfiguration oder über SET [LOCAL] für eine bestimmte Abfrage/transaktion erfolgen.

Beispiel:

SHOW work_mem;
-- "16MB"

SELECT
  random()
FROM
  generate_series(1, 1000000)
ORDER BY
  1;

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Wir korrigieren:

SET work_mem = '128MB'; -- vor Ausführung der Abfrage

Rezepte für fehlerhafte SQL-Abfragen
[sehen Sie sich explain.tensor.ru an]

Aus offensichtlichen Gründen, wenn nur der Speicher und nicht die Festplatte verwendet wird, wird die Abfrage viel schneller ausgeführt. Gleichzeitig wird auch ein Teil der Last von der HDD abgenommen.

Es ist wichtig zu verstehen, dass man nicht immer unbegrenzt viel Speicher zuweisen kann – es wird einfach nicht für alle ausreichen.

#9: неактуальная статистика

Wann tritt dies auf?

Die Datenbank wurde sofort mit vielen Daten gefüllt, doch es blieb keine Zeit, um sie zu verarbeiten. ANALYSE.

Wie man erkennt

-> Seq Scan | Bitmap Heap Scan | Index [Only] Scan [Backward]
   && Verhältnis >> 10

Empfehlungen

Tatsächlich durchführen ANALYSE.

Diese Situation ist ausführlicher beschrieben in PostgreSQL Antipatterns: Statistik hat Vorrang.

#10: «что-то пошло не так»

Wann tritt dies auf?

Es kam zu einer Blockierung durch eine konkurrierende Abfrage oder es standen nicht genügend CPU-/Hypervisor-Ressourcen zur Verfügung.

Wie man erkennt

-> *
   && (shared hit / 8K) + (shared read / 1K)  100ms -- wir haben wenig gelesen, aber zu lange

Empfehlungen

Verwenden Sie ein externes System zur Überwachung des Servers auf mögliche Blockierungen oder abnormalen Ressourcenverbrauch. Wir haben bereits über unsere Lösung zur Organisation dieses Prozesses für Hunderte von Servern berichtet. hier und hier.

Rezepte für fehlerhafte SQL-Abfragen
Rezepte für fehlerhafte SQL-Abfragen

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster