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
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
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.
| id (PK) | username | created_at | |
|---|---|---|---|
| 1 | aldric | aldric@mail.at | 2026-09-12 |
| 2 | brann | brann@mail.at | 2026-09-20 |
| 3 | cyra | NULL | 2026-10-01 |
Begriffe
| Begriff | Bedeutung |
|---|---|
| Spalte (Attribut) | Hat einen Namen und einen Datentyp: INT, VARCHAR, DECIMAL, DATE … |
| Zeile (Datensatz, Tupel) | Ein Eintrag, in der Tabelle oben grün umrandet. |
| Feld | Eine Zelle, also genau ein Wert. |
| NULL | Kein Wert. Nicht „leer“ und nicht 0. Vergleich nur mit IS NULL. |
Die Datenbank ist die Sammlung der Daten. Das DBMS (MySQL/MariaDB) ist die Software, die sie verwaltet, Queries ausführt und Rechte prüft.
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.
| id | username |
|---|
| player_id | guild_id | rang |
|---|
| id | name |
|---|
Beziehungsarten
| Typ | Beispiel | Umsetzung |
|---|---|---|
| 1 : 1 | Player ↔ player_stats | FK mit UNIQUE |
| 1 : n | Kategorie → Produkte | FK auf der n-Seite (products.category_id) |
| n : m | Players ↔ Gilden, Bestellung ↔ Produkte | Zwischentabelle mit zwei FKs (order_items) |
category_id steht in products, nicht umgekehrt.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;
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.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.
players. Das ist typisch, denn die zentrale Entität hat die meisten eingehenden FKs. Im Webshop wäre das users bzw. products.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.
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';
Gültiges SQL? Gibt's die Tabellen und Spalten?
Welche Tabelle zuerst? Index oder Full Scan? Siehst du mit EXPLAIN.
Joinen, filtern, gruppieren, sortieren nach Plan.
Datenseiten aus dem RAM (Buffer Pool) oder der .ibd-Datei.
Logische Reihenfolge
SQL arbeitet die Klauseln nicht in der Reihenfolge ab, in der du sie schreibst:
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
| Buchstabe | Bedeutung | Beispiel Webshop |
|---|---|---|
| Atomarität | Alles oder nichts | Bestellung und Lagerabzug passieren gemeinsam oder gar nicht. |
| Consistency | Regeln gelten danach wieder | FK, UNIQUE und CHECK sind nach COMMIT erfüllt. |
| Isolation | Keine halbfertigen Daten sehen | Zwei Kunden kaufen gleichzeitig das letzte Stück. |
| Dauerhaftigkeit | Nach COMMIT gespeichert | Auch bei Stromausfall. |
$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();
}
PHP ↔ MySQL – wie sie miteinander reden
Der Browser redet nie direkt mit MySQL. Dazwischen sitzen Apache und PHP. Verfolg ein Datenpaket durch login.php.
' OR 1=1 -- bleibt deshalb nur Text und wird nie zu SQL.Mit Prepared Statements in PDO. User-Input wird nie in den Query-String geklebt, sondern über Platzhalter (
? oder :name) übergeben.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.
| id | username | guild_id |
|---|
| id | name |
|---|
| p.id | p.username | p.guild_id | g.id | g.name |
|---|
| JOIN | Liefert | Typischer Einsatz |
|---|---|---|
| INNER | Nur Paare mit Treffer | Produkte mit ihrer Kategorie |
| LEFT | Alle links, rechts NULL wenn kein Treffer | Alle User, auch ohne Bestellung |
| RIGHT | Alle rechts, links NULL wenn kein Treffer | Selten, meist dreht man's zu LEFT um |
| CROSS | Jede mit jeder (kartesisches Produkt) | Kombinationen, z. B. Größe × Farbe |
LEFT JOIN orders ... WHERE orders.id IS NULL, oder alternativ mit NOT EXISTS.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.
| Normalform | Regel |
|---|---|
| 1NF | Jedes Feld hat genau einen atomaren Wert. Keine Listen, keine Wiederholungsgruppen. Es gibt einen Schlüssel. |
| 2NF | 1NF + jedes Nicht-Schlüssel-Attribut hängt vom ganzen Schlüssel ab. Betrifft nur zusammengesetzte Schlüssel. |
| 3NF | 2NF + kein Nicht-Schlüssel-Attribut hängt von einem anderen Nicht-Schlüssel-Attribut ab (keine transitive Abhängigkeit). |
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.
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.
AUTO_INCREMENT-Details, Engines oder User-Rechte gibt's hier nicht. Für die echte Prüfung testest du in phpMyAdmin.LAP-Spickzettel Datenbanken
| Thema | Das musst du sagen können |
|---|---|
| PK | Eindeutig, NOT NULL, identifiziert eine Zeile. Meist INT AUTO_INCREMENT. |
| FK | Verweist auf einen PK, sichert die referentielle Integrität. Sitzt auf der n-Seite. |
| n:m | Immer über eine Zwischentabelle mit zwei FKs. |
| Normalformen | 1NF atomar · 2NF ganzer Schlüssel · 3NF keine transitiven Abhängigkeiten |
| Anomalien | Update-, Insert- und Delete-Anomalie entstehen durch Redundanz. |
| WHERE vs HAVING | WHERE filtert Zeilen vor dem Gruppieren, HAVING filtert Gruppen danach. |
| Reihenfolge | FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT |
| JOINs | INNER = nur Treffer · LEFT = alle links · RIGHT = alle rechts · CROSS = jede × jede |
| Index | Beschleunigt Suche, JOIN und ORDER BY. Kostet bei INSERT/UPDATE. Gehört auf FKs. |
| ACID | Atomarität, Konsistenz, Isolation, Dauerhaftigkeit. In PHP: beginTransaction / commit / rollBack |
| InnoDB | Nur InnoDB kann FKs und Transaktionen, MyISAM nicht. |
| Sicherheit | Prepared Statements gegen SQL-Injection, password_hash für Passwörter, htmlspecialchars gegen XSS. |
| Datentypen | Geld als DECIMAL(10,2), nie FLOAT. Text als VARCHAR(n), lange Texte als TEXT. |