SQL · MySQL · Normalisierung – interaktiv
Tutorial

Datenbanken verstehen mit SQL & MySQL

Das hier ist die Ergänzung zum Webshop-Tutorial. Dort baust du den Shop, hier verstehst du, was in der Datenbank darunter passiert: wie sie aufgebaut ist, wie Schlüssel Tabellen verbinden, was eine Query intern durchläuft, wie PHP mit MySQL redet und wie du ein Schema sauber normalisierst.

Was du hier findest

  • Aufbau von Server, Datenbank, Tabelle und Feld
  • Primär- und Fremdschlüssel zum Anklicken
  • Beziehungsgraph einer echten Game-DB mit 17 Tabellen
  • Query-Pipeline: Parser, Optimizer, Executor, InnoDB
  • Login-Request als animiertes Datenpaket von PHP zu MySQL
  • INNER, LEFT, RIGHT und CROSS JOIN Schritt für Schritt
  • Normalisierung von 0NF bis 3NF
  • Ein SQL-Playground zum Selbertippen
So arbeitest du damit
1. Kapitel lesen
2. Interaktive Box ausprobieren (klicken, abspielen, ziehen)
3. Gleiche Idee im SQL-Playground selbst schreiben
4. Prüfer-Frage am Ende laut beantworten
Kapitelreihenfolge = Lernreihenfolge. Erst Aufbau und Schlüssel, dann der Ablauf, dann JOINs und Normalformen. Ohne PK/FK verstehst du keinen JOIN.
Kapitel 1

Aufbau einer Datenbank

Eine relationale DB ist eine Hierarchie. Der Server (bei XAMPP MariaDB) verwaltet mehrere Datenbanken, jede Datenbank hat Tabellen und jede Tabelle hat Zeilen und Spalten.

Vom Server bis zur Tabelle
DB-Server MariaDB / MySQL · localhost:3306
Datenbank grimhollow_db
playersplayer_statsguildsguild_membersitemszone_dropsencounters… 17 Tabellen
Datenbank webshop_otto
userscategoriesproductsordersorder_items
Anatomie einer Tabelle
players
id (PK)usernameemailcreated_at
1aldricaldric@mail.at2026-09-12
2brannbrann@mail.at2026-09-20
3cyraNULL2026-10-01

Begriffe

BegriffBedeutung
Spalte (Attribut)Hat einen Namen und einen Datentyp: INT, VARCHAR, DECIMAL, DATE …
Zeile (Datensatz, Tupel)Ein Eintrag, in der Tabelle oben grün umrandet.
FeldEine Zelle, also genau ein Wert.
NULLKein Wert. Nicht „leer“ und nicht 0. Vergleich nur mit IS NULL.
Prüfer-Frage: Was ist der Unterschied zwischen Datenbank und DBMS?
Die Datenbank ist die Sammlung der Daten. Das DBMS (MySQL/MariaDB) ist die Software, die sie verwaltet, Queries ausführt und Rechte prüft.
Kapitel 2

Primär- und Fremdschlüssel

Der Primärschlüssel (PK) identifiziert eine Zeile eindeutig und ist nie NULL. Ein Fremdschlüssel (FK) speichert den PK einer anderen Tabelle und verbindet die beiden Tabellen so.

ER-Diagramm – fahr über einen FK
playersPK id usernameemailcreated_at guild_membersFK player_id FK guild_idrang guildsPK id nameleader_id 1n n1 players ↔ guilds ist n:m. Die Zwischentabelle guild_members löst das in zwei 1:n-Beziehungen auf.
Schlüssel-Explorer – klick auf eine Zeile
players
idusername
guild_members
player_idguild_idrang
guilds
idname
PKFKverknüpfte Zeilen

Beziehungsarten

TypBeispielUmsetzung
1 : 1Player ↔ player_statsFK mit UNIQUE
1 : nKategorie → ProdukteFK auf der n-Seite (products.category_id)
n : mPlayers ↔ Gilden, Bestellung ↔ ProdukteZwischentabelle mit zwei FKs (order_items)
Achtung FK-Richtung: Der FK gehört immer auf die „viele“-Seite. category_id steht in products, nicht umgekehrt.
FK anlegen
CREATE TABLE guild_members (
  player_id INT NOT NULL,
  guild_id  INT NOT NULL,
  rang      VARCHAR(20),
  PRIMARY KEY (player_id, guild_id),
  FOREIGN KEY (player_id) REFERENCES players(id) ON DELETE CASCADE,
  FOREIGN KEY (guild_id)  REFERENCES guilds(id)  ON DELETE CASCADE
) ENGINE=InnoDB;
Prüfer-Frage: Was bewirkt ON DELETE CASCADE?
Wird der Player gelöscht, löscht die DB automatisch auch seine Zeilen in guild_members. Ohne CASCADE würde das Löschen mit einem FK-Fehler abbrechen.
Kapitel 3

Beziehungsgraph einer echten DB

Jeder Kasten ist eine Tabelle der grimhollow_db, jeder Pfeil ein FK, der auf den PK der Zieltabelle zeigt. Du kannst die Kästen ziehen. Klick auf eine Tabelle zeigt ihre Spalten und wer auf sie verweist.

grimhollow_db – 17 Tabellen (D3.js)
Player Gilde Spielwelt Chat
Was man sieht: Fast alles zeigt auf players. Das ist typisch, denn die zentrale Entität hat die meisten eingehenden FKs. Im Webshop wäre das users bzw. products.
Die Spalten sind aus den Tabellennamen abgeleitet. Vergleich sie mit deiner echten Struktur in phpMyAdmin.
Kapitel 4

Was bei einer Query passiert

SQL ist deklarativ: Du schreibst, was du willst, und den Weg zu den Daten sucht sich der Server selbst. Schick eine Query ab und schau zu, welche Stationen sie durchläuft.

Query-Pipeline
SELECT p.username, g.name
FROM players AS p
JOIN guild_members AS gm ON gm.player_id = p.id
JOIN guilds AS g ON g.id = gm.guild_id
WHERE g.name = 'Eisenfaust';
1 · ParserSyntax prüfen

Gültiges SQL? Gibt's die Tabellen und Spalten?

2 · OptimizerPlan wählen

Welche Tabelle zuerst? Index oder Full Scan? Siehst du mit EXPLAIN.

3 · ExecutorAusführen

Joinen, filtern, gruppieren, sortieren nach Plan.

4 · InnoDBSpeicher

Datenseiten aus dem RAM (Buffer Pool) oder der .ibd-Datei.

Warte auf eine Query …

Logische Reihenfolge

SQL arbeitet die Klauseln nicht in der Reihenfolge ab, in der du sie schreibst:

FROM / JOIN→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT
Prüfer-Frage: Warum kann ich einen SELECT-Alias nicht im WHERE verwenden?
Weil WHERE vor SELECT ausgeführt wird. Den Alias gibt's dort noch nicht. Im ORDER BY geht's, weil das danach kommt.

Transaktionen: ACID

BuchstabeBedeutungBeispiel Webshop
AtomaritätAlles oder nichtsBestellung und Lagerabzug passieren gemeinsam oder gar nicht.
ConsistencyRegeln gelten danach wiederFK, UNIQUE und CHECK sind nach COMMIT erfüllt.
IsolationKeine halbfertigen Daten sehenZwei Kunden kaufen gleichzeitig das letzte Stück.
DauerhaftigkeitNach COMMIT gespeichertAuch bei Stromausfall.
Transaktion in PHP (checkout.php)
$pdo->beginTransaction();
try {
    $pdo->prepare("INSERT INTO orders (user_id) VALUES (?)")->execute([$uid]);
    $orderId = $pdo->lastInsertId();
    // ... order_items einfügen ...
    $pdo->commit();
} catch (Exception $e) {
    $pdo->rollBack();
}
Kapitel 5

PHP ↔ MySQL – wie sie miteinander reden

Der Browser redet nie direkt mit MySQL. Dazwischen sitzen Apache und PHP. Verfolg ein Datenpaket durch login.php.

Login als Datenpaket
Rückweg: Resultset → PHP → HTML → Browser BrowserFormular ApacheWebserver :80 PHP + PDOlogin.php MySQLTCP :3306


Prepared Statement: Query und Daten gehen in getrennten Paketen zur DB (Schritt 3 und 4). Der Server kompiliert zuerst die Query mit Platzhaltern, dann kommen die Werte dazu. Ein eingegebenes ' OR 1=1 -- bleibt deshalb nur Text und wird nie zu SQL.
Prüfer-Frage: Wie schützt du dich vor SQL-Injection?
Mit Prepared Statements in PDO. User-Input wird nie in den Query-String geklebt, sondern über Platzhalter (? oder :name) übergeben.
Kapitel 6

JOINs in Zeitlupe

Ein JOIN vergleicht jede Zeile links mit jeder Zeile rechts und behält die Paare, bei denen die ON-Bedingung stimmt. Dieser Nested Loop ist die einfachste Variante, wie die Datenbank das intern macht.

JOIN-Simulation

  
playersguilds
Wähl einen JOIN-Typ und drück „Abspielen“.
players (links)
idusernameguild_id
guilds (rechts)
idname
Ergebnis · 0 Zeilen
p.idp.usernamep.guild_idg.idg.name
JOINLiefertTypischer Einsatz
INNERNur Paare mit TrefferProdukte mit ihrer Kategorie
LEFTAlle links, rechts NULL wenn kein TrefferAlle User, auch ohne Bestellung
RIGHTAlle rechts, links NULL wenn kein TrefferSelten, meist dreht man's zu LEFT um
CROSSJede mit jeder (kartesisches Produkt)Kombinationen, z. B. Größe × Farbe
Prüfer-Frage: Wie findest du alle Kunden ohne Bestellung?
LEFT JOIN orders ... WHERE orders.id IS NULL, oder alternativ mit NOT EXISTS.
Kapitel 7

Normalisierung in 4 Schritten

Ziel: Jede Information steht genau an einer Stelle. Dann gibt's keine Widersprüche beim Ändern, Einfügen oder Löschen (Anomalien). Beispiel: Bestellungen aus einem Webshop.

Klick dich durch die Normalformen
Primärschlüssel Fremdschlüssel Problem, das der nächste Schritt löst
NormalformRegel
1NFJedes Feld hat genau einen atomaren Wert. Keine Listen, keine Wiederholungsgruppen. Es gibt einen Schlüssel.
2NF1NF + jedes Nicht-Schlüssel-Attribut hängt vom ganzen Schlüssel ab. Betrifft nur zusammengesetzte Schlüssel.
3NF2NF + kein Nicht-Schlüssel-Attribut hängt von einem anderen Nicht-Schlüssel-Attribut ab (keine transitive Abhängigkeit).
Merksatz: „The key, the whole key, and nothing but the key.“ Das sind 1NF, 2NF und 3NF.
Prüfer-Frage: Warum steht in order_items der Preis, obwohl er schon in products steht?
Das ist kein Normalisierungsfehler. Der Preis zum Kaufzeitpunkt ist eine eigene Information. Ändert sich der Produktpreis später, muss die alte Rechnung trotzdem stimmen.
Referenz

SQL-Playground

Eine echte SQL-Engine (AlaSQL) läuft direkt im Browser, mit der Game-DB und dem fertig normalisierten Webshop aus Kapitel 7. Tipp deine Übungs-Queries ein. Kaputtmachen kannst du nichts, „DB zurücksetzen“ stellt alles wieder her.

Playground
Strg + Enter
AlaSQL ist nah an MySQL, aber nicht identisch. AUTO_INCREMENT-Details, Engines oder User-Rechte gibt's hier nicht. Für die echte Prüfung testest du in phpMyAdmin.
Referenz

LAP-Spickzettel Datenbanken

ThemaDas musst du sagen können
PKEindeutig, NOT NULL, identifiziert eine Zeile. Meist INT AUTO_INCREMENT.
FKVerweist auf einen PK, sichert die referentielle Integrität. Sitzt auf der n-Seite.
n:mImmer über eine Zwischentabelle mit zwei FKs.
Normalformen1NF atomar · 2NF ganzer Schlüssel · 3NF keine transitiven Abhängigkeiten
AnomalienUpdate-, Insert- und Delete-Anomalie entstehen durch Redundanz.
WHERE vs HAVINGWHERE filtert Zeilen vor dem Gruppieren, HAVING filtert Gruppen danach.
ReihenfolgeFROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
JOINsINNER = nur Treffer · LEFT = alle links · RIGHT = alle rechts · CROSS = jede × jede
IndexBeschleunigt Suche, JOIN und ORDER BY. Kostet bei INSERT/UPDATE. Gehört auf FKs.
ACIDAtomarität, Konsistenz, Isolation, Dauerhaftigkeit. In PHP: beginTransaction / commit / rollBack
InnoDBNur InnoDB kann FKs und Transaktionen, MyISAM nicht.
SicherheitPrepared Statements gegen SQL-Injection, password_hash für Passwörter, htmlspecialchars gegen XSS.
DatentypenGeld als DECIMAL(10,2), nie FLOAT. Text als VARCHAR(n), lange Texte als TEXT.
Letzter Check: Kannst du jede Zeile dieser Tabelle in einem Satz erklären, ohne hinzuschauen? Dann passt's für die Prüfung.