Zum Inhalt
wannacode

Langsame Abfragen

Kurz gesagt: Eine Abfrage bleibt trotz Index langsam, wenn die Spalte in einer Funktion oder in LIKE steckt, wenn sie alle Spalten holt, wenn ein OR auch eine Spalte ohne Index prüft oder wenn sie alles zählt, um „gibt es welche?“ zu beantworten.

Problem

Der Index ist da, und es ist trotzdem langsam

Der Jahresbericht zählt die Besuche im Jahr 2026. Es gibt einen Index auf seen_at. Führe die Abfrage aus und sieh dir die Zeit an:

SQLDatenbank: Shop

Und der Plan:

SQLDatenbank: Shop

SCAN visits USING COVERING INDEX. Die Datenbank liest den Index, weil er kleiner ist als die Tabelle, aber sie liest ihn ganz: alle 300 000 Einträge. Eine Suche gibt es nicht.

Die Idee

Die Spalte muss allein stehen

Der Index ist nach seen_at sortiert. Er ist nicht nach substr(seen_at, 1, 4) sortiert. Um die Bedingung zu prüfen, muss die Datenbank die Funktion für jeden Eintrag berechnen, und dafür muss sie alle durchgehen.

Dieselbe Bedingung lässt sich mit Grenzen schreiben, bei denen die Spalte auf einer Seite allein steht:

SQLDatenbank: Shop
SQLDatenbank: Shop

Das Ergebnis ist dasselbe, und der Plan ist SEARCH. Vergleiche die Zeiten der beiden Abfragen.

Dasselbe gilt für Arithmetik. customer_id + 0 = 1234 liefert dasselbe wie customer_id = 1234, aber der Index hilft nicht:

SQLDatenbank: Shop

Die Regel: Fass die Spalte in WHERE nicht an. Die Rechnung geht auf die andere Seite (price > 10 / 1.2, nicht price * 1.2 > 10), und eine Funktion auf einem Datum wird zu einem Bereich.

Probier es aus

Oktober

Im Feld steht die Abfrage aus dem Jahresbericht. Der Monatsbericht will die Besuche im Oktober 2026. Schreibe die Bedingung so, dass sie den Index auf seen_at nutzt.

SQLDatenbank: Shop

Sieh es dir an

LIKE und der Index

Es sieht so aus, als müsste LIKE '2026-10%' den Index nutzen: Der Anfang ist bekannt, wie im Telefonbuch. Sieh dir den Plan an:

SQLDatenbank: Shop

In SQLite ist es SCAN. Der Grund: LIKE unterscheidet in SQLite nicht zwischen Groß- und Kleinschreibung, während der Index mit dieser Unterscheidung sortiert ist. SQLite nutzt einen Index für LIKE 'anfang%' nur unter bestimmten Bedingungen, zum Beispiel wenn die Spalte oder der Index COLLATE NOCASE ist. Bei Daten ist der sichere Weg der Bereich aus der vorigen Karte.

Ein Muster, das mit % beginnt, kann in keiner Datenbank einen gewöhnlichen Index nutzen. Der Anfang ist unbekannt, die Suche hat also keinen Startpunkt:

SQLDatenbank: Shop
Genauer, für Neugierige

Die Ausnahme von der Regel über Funktionen ist ein Index auf einem Ausdruck: CREATE INDEX idx_visits_year ON visits(substr(seen_at, 1, 4)) macht aus WHERE substr(seen_at, 1, 4) = '2026' wieder ein SEARCH. Der Index hilft nur Abfragen mit demselben Ausdruck.

In SQLite unterscheidet GLOB '2026-10*' zwischen Groß- und Kleinschreibung und nutzt den Index (der Plan ist SEARCH). Das tut auch PRAGMA case_sensitive_like = ON, aber es ändert das Verhalten jedes LIKE auf der Verbindung.

In PostgreSQL hilft ein gewöhnlicher Index bei LIKE 'anfang%' nur mit der Kollation C oder wenn er mit text_pattern_ops angelegt wurde. Für die Suche mitten im Text gibt es eigene Arten von Indizes (die Erweiterung pg_trgm). Die Volltextsuche kommt im Modul über die Arten von Datenbanken.

Die Idee

Nur die Spalten, die du brauchst

Das Programm zeigt, an welchen Tagen die Kunden 1000 bis 1100 da waren. Zwei Abfragen mit denselben Zeilen. Vergleiche die Zeiten:

SQLDatenbank: Shop
SQLDatenbank: Shop

Die zweite ist schneller, weil customer_id und seen_at im Index (customer_id, seen_at) stehen und er sie abdeckt: Die Datenbank öffnet die Tabelle nicht. SELECT * will auch product_id, also geht die Datenbank für jeden der 6 060 Einträge zusätzlich zur Tabelle. Schreibe in SELECT nur die Spalten, die das Programm wirklich nutzt. So wandern auch weniger Daten zum Programm.

Die Idee

OR und IN

OR auf derselben Spalte ist dasselbe wie IN. SQLite führt beide gleich aus:

SQLDatenbank: Shop

Die Falle ist OR über verschiedene Spalten. Auf product_id gibt es keinen Index. Zeilen mit product_id = 4 können überall in der Tabelle stehen, also liest die Datenbank alles, und der Index für den Kunden hilft nicht:

SQLDatenbank: Shop

Haben beide Spalten einen Index, sucht SQLite in jedem einzeln und vereint die Ergebnisse (der Plan sagt MULTI-INDEX OR). Fehlt für eine Hälfte der Index, bleibt es bei SCAN.

Die Idee

„Gibt es welche?“ statt „wie viele?“

Die Seite von Produkt 13 zeigt „Beliebt“, wenn es jemand angesehen hat. Das Zählen findet alle 12 000 Besuche:

SQLDatenbank: Shop

Die Seite braucht nur „ja“ oder „nein“. EXISTS hört bei der ersten gefundenen Zeile auf:

SQLDatenbank: Shop

Auf product_id gibt es keinen Index, aber EXISTS ist trotzdem schnell, weil der erste Besuch des Produkts nahe am Anfang der Tabelle liegt. Gibt es keine solche Zeile, liest auch EXISTS alles. Auch hier hilft also ein Index. Ein anderer Weg ist SELECT 1 FROM … WHERE … LIMIT 1: Ein leeres Ergebnis bedeutet „nein“.

Übung

Zwei Aufgaben

1. Im Feld steht die alte Abfrage für 2026. Kunde 1234 fragt, wie oft er 2025 Produkte angesehen hat. Schreibe sie so, dass der Plan sowohl beim Kunden als auch beim Datum ein SEARCH ist.

Die Besuche von Kunde 1234 im Jahr 2025, mit SEARCH auf beiden Spalten:

SQLDatenbank: Shop

2. Der Knopf „Nochmal ansehen“ wird gezeigt, wenn Kunde 4321 ab dem 1. Oktober 2026 ein Produkt angesehen hat. Gib 1 oder 0 in einer Spalte seen zurück.

Hat Kunde 4321 Besuche ab 2026-10-01?

SQLDatenbank: Shop

Test

Teste dich

1. Es gibt einen Index auf seen_at. Welche Bedingung kann ihn für eine Suche nutzen?
2. Warum nutzt WHERE customer_id + 0 = 1234 den Index auf customer_id nicht?
3. Das Programm will wissen, ob der Kunde das Produkt überhaupt angesehen hat. Welche Abfrage passt am besten?
4. Es gibt einen Index auf customer_id und keinen auf product_id. Wie ist der Plan von WHERE customer_id = 1234 OR product_id = 4?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·