Häufige SQL-Antipatterns und warum sie schaden
AI generated
SELECT
JOIN
SQL / Datenmodellierung
Häufige SQL-Antipatterns
und warum sie in der Praxis so viel Schaden anrichten

Manche Datenbankprobleme sind keine Frage von fehlenden Indizes oder schlecht formulierten Abfragen, sondern liegen tiefer im Schema-Design selbst begründet. Diese strukturellen Antipatterns wiederholen sich über Projekte und Branchen hinweg auffallend gleichförmig: eine generische Attributtabelle, die jede Validierung unmöglich macht, eine Spalte pro Wiederholung statt einer eigenen Zeile, ein Fremdschlüssel ohne begleitenden Index. Jedes dieser Muster beginnt harmlos und pragmatisch, wird aber mit wachsendem Datenvolumen zu einem handfesten Performance- und Wartungsproblem. Diese Sammlung ordnet die häufigsten Fälle nach Symptom, Ursache und Refactoring-Weg.

12 Min. Lesezeit SQL-Antipatterns Schema-Design Refactoring

1. Warum sich strukturelle Antipatterns so hartnäckig halten

Ein Antipattern unterscheidet sich von einem einfachen Bug dadurch, dass es zunächst funktioniert. Die Abfragen liefern korrekte Ergebnisse, die Anwendung läuft, die ersten Tests sind grün. Das Problem zeigt sich erst mit wachsendem Datenvolumen, steigender Nutzerzahl oder wenn ein neues Feature auf der vorhandenen Struktur aufbauen soll und plötzlich unverhältnismäßig viel Aufwand kostet. Diese Verzögerung zwischen Entscheidung und sichtbarem Schaden macht Antipatterns so schwer zu erkennen, bevor sie zum echten Problem werden.

Ein zweiter Grund ist, dass viele dieser Muster aus einer nachvollziehbaren, kurzfristigen Motivation entstehen: eine generische Lösung, die angeblich jede zukünftige Anforderung abdeckt, eine schnelle Spalte, die eine Migration erspart, ein fehlender Index, der beim Prototyping schlicht vergessen wurde. Erst im Nachhinein zeigt sich, dass die vermeintliche Flexibilität oder Zeitersparnis mit erheblichen Folgekosten erkauft wurde.

Diese Sammlung konzentriert sich bewusst auf strukturelle Antipatterns im Schema-Design, nicht auf einzelne fehlerhafte Abfragen. Der Unterschied ist wichtig: Eine schlechte Abfrage lässt sich isoliert reparieren, ein schlechtes Schema zieht sich durch die gesamte Anwendung und erfordert eine koordinierte Migration.

2. Antipattern 1: EAV-Missbrauch für Daten mit bekanntem Schema

Das Entity-Attribute-Value-Modell speichert Attribute nicht als eigene Spalten, sondern als Zeilen in einer generischen Tabelle mit den Spalten entity_id, attribute_name und value. Für wirklich dynamische, zur Entwurfszeit unbekannte Attribute, etwa benutzerdefinierte Produktmerkmale in einem Katalogsystem, ist dieses Modell legitim. Missbraucht wird es, wenn Entwickler es einsetzen, um Schema-Änderungen grundsätzlich zu vermeiden, auch für Attribute, die von Anfang an bekannt und stabil sind, wie Name, Preis oder Erstellungsdatum eines Datensatzes.

Die Symptome zeigen sich schnell: Jede sinnvolle Abfrage erfordert mehrfache Self-Joins auf dieselbe Tabelle, einen pro abgefragtem Attribut. Typprüfung und NOT-NULL-Constraints funktionieren nicht mehr, weil die value-Spalte alles als Text oder generischen Typ speichern muss. Aggregationen wie Durchschnittswerte werden zum Albtraum, weil numerische Werte erst aus Text geparst werden müssen. Der Optimierer kann kaum sinnvolle Statistiken über eine derart generische Struktur führen.

Der Refactoring-Weg führt zurück zu einem klassischen relationalen Schema mit dedizierten Spalten für bekannte, stabile Attribute. Nur echte, zur Entwurfszeit unbekannte Erweiterbarkeit rechtfertigt EAV, und selbst dann lohnt sich oft ein hybrider Ansatz: feste Spalten für den Kern, eine begrenzte EAV-Struktur oder ein JSON-Spaltentyp nur für die tatsächlich variablen Zusatzattribute.


-- Antipattern: jedes Attribut erfordert einen weiteren Join
SELECT e.entity_id,
       n.value AS name,
       p.value AS price,
       d.value AS created_at
FROM entities e
LEFT JOIN attribute_values n ON n.entity_id = e.entity_id AND n.attribute_name = 'name'
LEFT JOIN attribute_values p ON p.entity_id = e.entity_id AND p.attribute_name = 'price'
LEFT JOIN attribute_values d ON d.entity_id = e.entity_id AND d.attribute_name = 'created_at';

-- Refactoring: bekannte, stabile Attribute als eigene Spalten
SELECT entity_id, name, price, created_at
FROM entities;

3. Antipattern 2: Spalten statt Zeilen für wiederholte Werte

Dieses Muster zeigt sich oft in Tabellen mit Spalten wie phone_1, phone_2, phone_3 oder tag_1 bis tag_5. Die Motivation ist meist, eine 1:n-Beziehung zu vermeiden, weil sie zusätzliche Joins bedeutet. Das Ergebnis ist eine feste, willkürliche Obergrenze für die Anzahl der Werte und eine Abfrage, die für jede mögliche Spalte separat geschrieben werden muss, wenn nach einem bestimmten Wert gesucht wird, egal in welcher Spalte er steht.

Konkrete Symptome sind lange OR-Ketten in WHERE-Klauseln, die alle nummerierten Spalten einzeln abfragen, sowie Anwendungscode, der bei Erreichen des sechsten Tags oder der vierten Telefonnummer schlicht keine Möglichkeit mehr hat, den Wert zu speichern. Auch UPDATE-Anweisungen werden unübersichtlich, weil man beim Hinzufügen eines Werts erst prüfen muss, welche der nummerierten Spalten noch frei ist.

Der saubere Weg ist eine separate Tabelle mit einer Zeile pro Wert und einem Fremdschlüssel auf die Haupttabelle, also eine klassische 1:n-Modellierung. Das macht die Anzahl der Werte unbegrenzt, vereinfacht Abfragen auf eine einzige WHERE-Bedingung und erlaubt einen Index direkt auf der Wert-Spalte, was bei der nummerierten Variante praktisch unmöglich ist.


-- Antipattern: feste Obergrenze und unübersichtliche Suche
-- Tabelle: contacts(id, phone_1, phone_2, phone_3)
SELECT id FROM contacts
WHERE phone_1 = '+491701234567'
   OR phone_2 = '+491701234567'
   OR phone_3 = '+491701234567';

-- Refactoring: eigene Tabelle, unbegrenzte Anzahl, indizierbar
CREATE TABLE contact_phones (
  id         BIGINT PRIMARY KEY,
  contact_id BIGINT NOT NULL REFERENCES contacts(id),
  phone      VARCHAR(32) NOT NULL
);
CREATE INDEX idx_contact_phones_phone ON contact_phones(phone);

SELECT contact_id FROM contact_phones WHERE phone = '+491701234567';

4. Antipattern 3: Fremdschlüssel ohne begleitenden Index

Ein Fremdschlüssel-Constraint garantiert referenzielle Integrität, erzeugt aber nicht automatisch einen Index auf der referenzierenden Spalte, das hängt vom konkreten Datenbanksystem ab und ist keineswegs überall selbstverständlich. Fehlt dieser Index, wird jeder Join über die Fremdschlüsselbeziehung zu einem vollständigen Tabellenscan auf der referenzierenden Tabelle, statt einen gezielten Indexzugriff zu nutzen.

Das Symptom fällt oft erst bei wachsendem Datenvolumen auf: Eine Abfrage, die bei tausend Zeilen unauffällig schnell war, wird bei einer Million Zeilen spürbar langsam, ohne dass sich am SQL-Code etwas geändert hätte. Besonders tückisch ist das bei DELETE-Operationen auf der referenzierten Tabelle, weil die Datenbank vor dem Löschen prüfen muss, ob abhängige Zeilen existieren, eine Prüfung, die ohne Index auf der Fremdschlüsselspalte ebenfalls einen vollständigen Scan auslöst.

Die Behebung ist meist unkompliziert: einen Index auf jeder Fremdschlüsselspalte anlegen, sofern das Datenbanksystem ihn nicht ohnehin automatisch erzeugt. Bei zusammengesetzten Fremdschlüsseln sollte die Spaltenreihenfolge im Index der häufigsten Abfragerichtung entsprechen. Eine regelmäßige Prüfung, welche Fremdschlüssel ohne begleitenden Index existieren, gehört in jede Schema-Review-Routine.


-- Fremdschlüssel ohne Index: Join und DELETE-Check werden zum Tabellenscan
ALTER TABLE order_items
  ADD CONSTRAINT fk_order_items_order
  FOREIGN KEY (order_id) REFERENCES orders(id);

-- Fehlender, aber notwendiger Index
CREATE INDEX idx_order_items_order_id ON order_items(order_id);

5. Antipattern 4: polymorphe Assoziationen ohne Datenbankprüfung

Bei einer polymorphen Assoziation referenziert eine Spalte wie commentable_id abhängig von einer zweiten Spalte commentable_type mal die Tabelle posts, mal die Tabelle photos. Auf Anwendungsebene wirkt das flexibel, auf Datenbankebene ist es problematisch: Ein klassischer Fremdschlüssel-Constraint kann nicht bedingt auf zwei unterschiedliche Zieltabellen verweisen, die referenzielle Integrität muss also komplett in der Anwendungslogik nachgebildet werden.

Das Symptom zeigt sich als verwaiste Datensätze, sogenannte Orphans, sobald irgendwo in der Anwendungslogik ein Löschvorgang die Prüfung vergisst oder ein Bug die type-Spalte falsch setzt. Solche Inkonsistenzen fallen oft erst auf, wenn ein Report plötzlich Kommentare zu nicht mehr existierenden Beiträgen anzeigt, und die Fehlersuche ist mühsam, weil die Datenbank selbst keine Fehlermeldung dazu liefert.

Ein sauberer Ersatz ist eine eigene Zuordnungstabelle pro Zielentität, etwa post_comments und photo_comments, jeweils mit einem echten Fremdschlüssel-Constraint. Wo eine gemeinsame Abfrage über beide Typen nötig ist, lässt sich das per UNION oder über eine View lösen, ohne die referenzielle Integrität auf Datenbankebene zu opfern.

6. Antipattern 5: kommagetrennte Werte in einer einzelnen Spalte

Eine Spalte wie tags mit dem Inhalt 'sql,performance,index' spart auf den ersten Blick eine zusätzliche Tabelle. In der Praxis erzeugt sie aber eine Reihe handfester Probleme: Um nach einem einzelnen Tag zu suchen, ist eine LIKE-Abfrage mit Wildcards nötig, die keinen regulären Index nutzen kann und daher fast immer einen vollständigen Tabellenscan bedeutet. Ein Tag zu entfernen oder umzubenennen erfordert String-Manipulation statt eines einfachen UPDATE oder DELETE.

Auch die referenzielle Integrität geht verloren: Nichts hindert die Anwendung daran, einen Tippfehler wie 'perfromance' einzufügen, der dann als eigener, nie wieder auffindbarer Wert im System existiert. Aggregationen wie das Zählen, wie oft jedes Tag verwendet wird, erfordern aufwendiges String-Splitting, das je nach Datenbanksystem unterschiedlich gut unterstützt wird und selten performant ist.

Die Lösung ist wie bei den wiederholten Spalten eine klassische Zuordnungstabelle, hier item_tags mit den Spalten item_id und tag, oder bei einer festen Menge möglicher Werte eine normalisierte tags-Tabelle mit Fremdschlüsselbeziehung. Für Datenbanksysteme mit nativer Array- oder JSON-Unterstützung kann in Einzelfällen ein typisiertes Array-Feld mit passendem Index eine akzeptable Zwischenlösung sein, eine kommagetrennte Textspalte ist es nicht.


-- Antipattern: Suche erfordert Wildcard-Scan
SELECT id FROM items WHERE tags LIKE '%performance%';

-- Refactoring: normalisierte Zuordnungstabelle
CREATE TABLE item_tags (
  item_id BIGINT NOT NULL REFERENCES items(id),
  tag     VARCHAR(64) NOT NULL,
  PRIMARY KEY (item_id, tag)
);
CREATE INDEX idx_item_tags_tag ON item_tags(tag);

SELECT item_id FROM item_tags WHERE tag = 'performance';

7. Symptome, die auf ein strukturelles Antipattern hindeuten

Ein wiederkehrendes Warnsignal ist, wenn Anwendungscode ungewöhnlich viel Logik enthält, um Daten zu bereinigen, zu parsen oder zusammenzusetzen, die eigentlich die Datenbank selbst übernehmen sollte. Muss eine Anwendung regelmäßig Text splitten, um mehrere Werte aus einer Spalte zu extrahieren, oder mehrfach dieselbe Prüfung wiederholen, die ein Fremdschlüssel-Constraint automatisch erledigen würde, deutet das auf ein strukturelles Problem im Schema hin, nicht auf einen Fehler im Anwendungscode.

Ein zweites Signal sind Abfragen, die mit wachsender Datenmenge überproportional langsamer werden, obwohl die Anzahl der zurückgegebenen Zeilen konstant bleibt. Das deutet fast immer auf einen fehlenden Index oder eine Struktur hin, die keinen effizienten Indexzugriff erlaubt, wie es bei EAV-Tabellen oder kommagetrennten Spalten typisch ist.

Ein drittes Signal ist organisatorisch: Wenn jede neue Anforderung, ein weiteres Attribut zu speichern, eine Migration mit ALTER TABLE erfordert und Teams diese Migrationen deshalb fürchten oder vermeiden, ist das oft ein Symptom dafür, dass irgendwo im Schema bereits ein Antipattern als vermeintliche Abkürzung existiert, das genau dieses Problem eigentlich lösen sollte.

8. Strategie für ein sicheres, schrittweises Refactoring

Ein Schema-Refactoring in einer produktiven Anwendung sollte niemals als einzelner, großer Big-Bang-Schritt erfolgen. Der bewährte Weg ist, die neue Struktur parallel zur alten aufzubauen, den Anwendungscode schrittweise auf lesenden Zugriff über die neue Struktur umzustellen, während Schreibvorgänge übergangsweise in beide Strukturen gehen, und erst nach vollständiger Migration die alte Struktur zu entfernen.

Für jedes der oben beschriebenen Muster empfiehlt sich vor Beginn eine Bestandsaufnahme: Wie viele Zeilen sind betroffen, welche Anwendungsteile lesen und schreiben aktuell darauf, und gibt es Reports oder externe Integrationen, die auf der alten Struktur basieren. Diese Bestandsaufnahme verhindert, dass eine Migration unerwartet einen entfernten Report bricht, der niemand mehr auf dem Schirm hatte.

Automatisierte Tests, die das Verhalten vor und nach der Migration vergleichen, sind hier besonders wertvoll, weil ein Schema-Refactoring naturgemäß viele Abfragen gleichzeitig betrifft. Wo möglich, lohnt sich außerdem, die Migration in mehreren kleinen, unabhängig deploybaren Schritten durchzuführen, statt alle betroffenen Tabellen in einem einzigen Release umzubauen.

9. Wann ein Antipattern bewusst in Kauf genommen werden darf

Nicht jede Abweichung von der reinen Lehre ist automatisch ein Fehler. Ein Prototyp, der innerhalb weniger Wochen wieder verworfen wird, profitiert womöglich mehr von schneller Entwicklung als von einem sauberen Schema. Eine kommagetrennte Spalte für ein internes Debug-Feld, das nie in einer WHERE-Klausel auftaucht, ist harmlos, weil keines der beschriebenen Symptome jemals eintritt.

Entscheidend ist, dass diese Ausnahmen bewusst getroffen und dokumentiert werden, statt aus Unwissen zu entstehen. Ein Kommentar im Migrationsskript, der erklärt, warum an dieser Stelle bewusst von der Norm abgewichen wird und unter welchen Bedingungen das zu überdenken ist, spart dem nächsten Entwickler viel Zeit und verhindert, dass eine bewusste Kurzfrist-Entscheidung versehentlich zum dauerhaften, unreflektierten Standard wird.

Der entscheidende Unterschied zwischen einer legitimen Abkürzung und einem echten Antipattern liegt also weniger in der Struktur selbst als in der bewussten Abwägung, die dahintersteht, und der Bereitschaft, sie zu revidieren, sobald sich die Rahmenbedingungen ändern.

Antipattern Typisches Symptom Ursache Refactoring-Weg
EAV-Missbrauch mehrfache Self-Joins pro Attribut vermeintliche Flexibilität für stabile Attribute dedizierte Spalten für bekannte Attribute
Spalten statt Zeilen OR-Ketten, feste Obergrenze Vermeidung einer 1:n-Beziehung eigene Tabelle mit Fremdschlüssel
Fehlender FK-Index langsame Joins ab mittlerem Datenvolumen Constraint erzeugt nicht automatisch Index Index explizit auf FK-Spalte anlegen
Polymorphe Assoziation verwaiste Datensätze, keine DB-Prüfung ein Constraint für zwei Zieltabellen unmöglich eigene Zuordnungstabelle pro Zieltyp
Kommagetrennte Spalte LIKE-Scan, String-Splitting bei Aggregation vermiedene zusätzliche Tabelle normalisierte Zuordnungstabelle

Mironsoft

Datenbank-Optimierung, Query-Tuning und Migrationen

SQL-Abfragen, die bei Wachstum immer langsamer werden?

Wir analysieren und optimieren SQL-Datenbanken unabhängig vom eingesetzten System, planen sichere Migrationen und Schema-Änderungen und bringen Teams Query-Optimierung praxisnah bei.

Query-Optimierung

Langsame Abfragen analysieren und mit Indizes und Explain-Plänen gezielt beschleunigen.

Migrations-Planung

Schema-Änderungen und Datenmigrationen sicher und ohne Downtime umsetzen.

Team-Schulung

SQL-Grundlagen und Performance-Denken praxisnah im Entwicklerteam verankern.

10. Zusammenfassung

SQL-Antipatterns: Das Wichtigste auf einen Blick

Kernidee

Strukturelle Schema-Fehler wirken lange harmlos und werden erst mit wachsendem Datenvolumen sichtbar.

Häufigste Ursache

Kurzfristige Flexibilität oder Zeitersparnis wird mit langfristigen Folgekosten erkauft.

Frühwarnsignal

Anwendungscode übernimmt Aufgaben, die eigentlich die Datenbank leisten sollte.

Vorgehen

Schrittweises Refactoring mit paralleler Struktur statt riskantem Big-Bang-Umbau.

11. FAQ: SQL-Antipatterns: Das Wichtigste auf einen Blick

1Ist das Entity-Attribute-Value-Modell immer ein Antipattern?
Nein. Für wirklich dynamische, zur Entwurfszeit unbekannte Attribute ist EAV legitim. Zum Antipattern wird es erst, wenn es für Attribute eingesetzt wird, die von Anfang an bekannt und stabil sind, wie Name oder Preis eines Datensatzes.
2Warum erzeugt ein Fremdschlüssel nicht automatisch einen Index?
Das hängt vom jeweiligen Datenbanksystem ab. Ein Fremdschlüssel-Constraint sichert referenzielle Integrität, erzwingt aber nicht zwingend einen begleitenden Index auf der referenzierenden Spalte. Ohne diesen Index wird jeder Join über die Beziehung zu einem vollständigen Tabellenscan.
3Wie erkenne ich kommagetrennte Werte als Antipattern in bestehendem Code?
Ein klares Signal sind LIKE-Abfragen mit führenden oder umschließenden Wildcards auf einer Textspalte, sowie Anwendungscode, der regelmäßig Strings anhand von Trennzeichen splittet, um einzelne Werte zu extrahieren.
4Was ist der Unterschied zwischen wiederholten Spalten und einer echten 1:n-Beziehung?
Wiederholte Spalten wie phone_1 bis phone_3 setzen eine feste, willkürliche Obergrenze und erschweren Suche und Indexierung. Eine echte 1:n-Beziehung über eine separate Tabelle erlaubt eine unbegrenzte Anzahl an Werten und einen direkten Index auf der Wert-Spalte.
5Warum sind polymorphe Assoziationen auf Datenbankebene problematisch?
Ein klassischer Fremdschlüssel kann nicht bedingt auf zwei unterschiedliche Zieltabellen verweisen. Referenzielle Integrität muss deshalb komplett in der Anwendungslogik nachgebildet werden, was bei fehlerhaftem Code zu verwaisten Datensätzen führt.
6Wie migriere ich ein bestehendes Schema weg von einem Antipattern, ohne Downtime zu verursachen?
Die neue Struktur wird parallel zur alten aufgebaut, Lesezugriffe werden schrittweise umgestellt, Schreibzugriffe gehen übergangsweise in beide Strukturen, und die alte Struktur wird erst nach vollständiger Migration entfernt.
7Gibt es Fälle, in denen ein Antipattern akzeptabel bleibt?
Ja, etwa bei kurzlebigen Prototypen oder internen Feldern, die niemals in einer WHERE-Klausel oder Aggregation verwendet werden. Entscheidend ist, dass diese Ausnahme bewusst getroffen und dokumentiert wird, statt aus Unwissenheit zu entstehen.
8Wie finde ich fehlende Indizes auf Fremdschlüsselspalten in einer bestehenden Datenbank?
Die meisten Datenbanksysteme bieten Systemkataloge oder Werkzeuge, die Fremdschlüssel-Constraints ohne begleitenden Index auflisten. Eine regelmäßige Prüfung dieser Liste sollte fester Bestandteil einer Schema-Review-Routine sein.
9Warum sind lange OR-Ketten über nummerierte Spalten ein Warnsignal?
Sie zeigen, dass eine wiederholte 1:n-Beziehung fälschlich als feste Anzahl von Spalten modelliert wurde. Jede zusätzliche Spalte erfordert eine weitere OR-Bedingung, und ab einer bestimmten Anzahl wird kein neuer Wert mehr gespeichert werden können.
10Kann ein JSON-Spaltentyp die beschriebenen Antipatterns ersetzen?
Für wirklich variable, seltene Zusatzattribute kann ein JSON-Feld eine akzeptable Ergänzung sein. Für Attribute mit bekanntem Schema, häufigen Abfragen oder Aggregationsbedarf bleibt eine normalisierte, indizierbare Spaltenstruktur die robustere Wahl.