SQL Tuning für Einsteiger – Explain, Indexe und Joins
Deine Abfrage läuft, aber zu langsam. Die Datenbank arbeitet hart, doch die Antwortzeit passt nicht. Bevor du Hardware aufrüstest, lohnt sich ein Blick auf Explain, Indexe und saubere Joins. Schon kleine Anpassungen bringen oft große Effekte.
Dieser Guide erklärt dir Schritt für Schritt, wie du Query-Pläne liest, passende Indexe setzt und Joins so formulierst, dass der Optimizer leichtes Spiel hat. Alles in einfacher Sprache, mit praxisnahen Beispielen, damit du direkt loslegen kannst.
Wie ein Datenbank-Optimizer denkt
Ein Optimizer schätzt Kosten: Wie viele Zeilen werden gelesen, welche Indexe passen, welches Join-Verfahren ist am schnellsten. Grundlage sind Statistiken über die Verteilung deiner Werte. Je besser diese Statistiken, desto klüger der Plan. Halte sie mit Befehlen wie ANALYZE aktuell.
Explain lesen – was der Plan dir sagt
Explain Basics
Ein Explain-Plan zeigt die Schritte der Abfrage. Achte auf:
- Zugriffsart: Seq Scan/Table Scan vs Index Scan
- Filter: Bedingungen, die Zeilen aussortieren
- Joins: Nested Loop, Hash Join, Merge Join
- Schätzwerte: Zeilenanzahl, Kosten, manchmal Zeit
Beispiel PostgreSQL:
EXPLAIN ANALYZE
SELECT o.id, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.created_at >= date_trunc('month', now());
Siehst du Seq Scan auf einer großen Tabelle mit Zeitfilter, fehlt oft ein Index auf created_at oder ein kombinierter Index.
Häufige Aha-Momente
Wenn Zeilenschätzungen stark vom Ist abweichen, sind Statistiken alt oder der Filter unglücklich. Aktualisiere Stats und prüfe Selektivität deiner Bedingungen. Eine Bedingung, die nur wenige Zeilen übrig lässt, ist indexfreundlich.
Indexe – die große Wirkung mit wenig Aufwand
B-Tree als Standard
Der B-Tree-Index beschleunigt Vergleiche wie =, <, >, BETWEEN und ORDER BY. Er ist dein Allrounder für Primärschlüssel, Fremdschlüssel, Zeitstempel und Sortierungen.
-- schneller filtern nach Datum
CREATE INDEX idx_orders_created_at ON orders(created_at);
Zusammengesetzte Indexe
Bei mehreren Spalten entscheidet die Reihenfolge. Setze das selektivste Feld nach vorn oder wähle die Reihenfolge passend zur Abfrage.
-- häufig: Kunde + aktueller Zeitraum
CREATE INDEX idx_orders_customer_created
ON orders(customer_id, created_at);
Wichtig: Ein Index auf (customer_id, created_at) hilft customer_id allein, aber nicht automatisch nur created_at.
Covering Indexe
Wenn alle benötigten Spalten im Index stehen, kann die DB die Tabelle nicht mehr lesen. In PostgreSQL mit INCLUDE, in MySQL über zusätzliche Indexspalten.
-- Lookup ohne Tabellenzugriff
CREATE INDEX idx_orders_lookup
ON orders(customer_id, created_at) INCLUDE(total_amount);
Partielle Indexe
Wenn du immer wieder die gleichen Teilmengen abfragst, nutze partielle Indexe.
CREATE INDEX idx_orders_open
ON orders(customer_id)
WHERE status = 'open';
Typische Index-Fallen
- Gemischte Datentypen (Text vs Zahl) verhindern Matches
- Funktionen wie LOWER(col) brauchen Funktionsindexe
- LIKE ‚%abc‘ ohne Präfix hilft B-Tree nicht – nutze Volltext oder Trigramme
Joins optimieren – korrekt verbinden, richtig filtern
Join-Verfahren verstehen
- Nested Loop: gut, wenn äußere Menge klein und innerer Zugriff indexiert ist
- Hash Join: stark bei großen Mengen ohne passende Sortierung
- Merge Join: effizient, wenn beide Seiten sortiert sind oder Indexe die Sortierung liefern
Du musst das Verfahren nicht erzwingen. Gib dem Optimizer die Indexe und Filter, die ihn dahin führen.
Filter früh anwenden
Schreibe Bedingungen so, dass sie vor dem Join greifen können.
-- besser
SELECT ...
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.created_at >= '2025-01-01';
-- schlechter: Filter erst nach großem Join
SELECT ...
FROM orders o
JOIN customers c ON c.id = o.customer_id
-- ohne Datum im WHERE: mehr Daten schieben
ON vs WHERE
Semantik zählt: In INNER JOIN ist es egal. Bei LEFT JOIN gehören join-spezifische Bedingungen in ON, damit du linke Zeilen nicht versehentlich entfernst.
-- korrekt bei LEFT JOIN
SELECT c.id, o.id
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id
AND o.created_at >= '2025-01-01';
EXISTS statt IN
EXISTS ist oft schneller und klarer, wenn du Existenz prüfen willst.
SELECT c.id
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
AND o.created_at >= '2025-01-01'
);
Pagination ohne Bremse
OFFSET liest erst, dann verwirft es Zeilen. Besser ist Keyset Pagination mit einem stabilen Sortierschlüssel.
-- schneller: seek pagination
SELECT id, created_at
FROM orders
WHERE created_at > :last_seen
ORDER BY created_at
LIMIT 50;
Schreibstil der Abfrage – kleine Regeln mit großer Wirkung
Nur holen, was du brauchst
SELECT * lädt unnötige Spalten. Greife gezielt zu, dann passen mehr Daten in Cache und Indexe wirken besser.
Prädikate sargable machen
Schreibe Bedingungen, die den Index nutzen können.
-- sargable
WHERE created_at >= '2025-01-01'
-- nicht sargable
WHERE date(created_at) >= '2025-01-01' -- Funktion auf Spalte verhindert Index
CTEs und Views
CTEs werden je nach DB materialisiert oder inline. Materialisierung kann bremsen. Prüfe mit Explain, ob die CTE wirklich hilft oder ob eine Subquery besser ist.
Wartung und Statistiken – die Basis für gute Pläne
Halte Statistiken frisch (ANALYZE), säubere Tabellen mit VACUUM/OPTIMIZE, prüfe Index-Bloat und entferne unbenutzte Indexe. Ohne aktuelle Stats geraten Kostenschätzungen daneben und gute Indexe werden ignoriert.
Mini-Fahrplan für deinen ersten Tuning-Run
- Wähle eine langsame Abfrage.
- Explain Analyze ausführen und den teuersten Schritt identifizieren.
- Passenden Index setzen oder Filter umformulieren.
- Plan erneut prüfen, Laufzeit messen, Änderung behalten oder verwerfen.
- Zum nächstteuren Schritt gehen und wiederholen.
Fazit
Gute Performance ist kein Zufall. Wenn du Explain-Pläne liest, passende Indexe wählst und Joins bewusst formulierst, gewinnst du oft Sekunden pro Abfrage zurück. Arbeite iterativ, halte Statistiken aktuell und denke an saubere Prädikate. So werden deine SQL-Abfragen verlässlich schnell und bleiben gut wartbar.



Hinterlasse einen Kommentar
An der Diskussion beteiligen?Hinterlasse uns deinen Kommentar!