Be Software Architecture Wise
Be Wise Apps

Read in English · Leer en español · Lire en français

Abschnitt 0.5 von 0.8 · Einsteiger · 18 Min. Lesezeit

Dateien und Datenbanken: relationale und nicht relationale Speicherung, Abfragen und Transaktionen

Videolektion

5 Min. · auf Englisch gesprochen, mit Untertiteln
Nur anhören (Audio)
Transkript lesen

Ein laufendes Programm hält seine Arbeitsdaten im Arbeitsspeicher, der alles vergisst, wenn das Programm endet. Alles, was das überstehen muss, etwa ein Kundenkonto oder eine Bestellung, muss in einen Speicher geschrieben werden. Stellen Sie sich eine Datenbank als Bibliothek mit einer sorgfältigen Bibliothekarin vor. Sie führt einen Katalog, prüft, dass jeder Datensatz den Regeln folgt, und verliert nie eine Änderung, deren Speicherung sie zugesagt hat.

Die einfachste Art, Daten aufzubewahren, ist eine Datei. Dateien eignen sich hervorragend für alles, was von Natur aus ein ganzes Dokument ist, etwa Bilder, PDF-Dateien, Videos und Logs. Aber stellen Sie sich einen Shop vor, der alle Bestellungen in einer einzigen Tabellenkalkulationsdatei führt. Zwei Kunden bezahlen im selben Moment. Wessen Änderung gewinnt? Der Server stürzt mitten beim Speichern ab. Ist die Datei jetzt halb alt und halb neu? Und jemand tippt eine Kundennummer ein, die es nicht gibt. Wer hält ihn auf? Jedes Programm, das die Datei nutzt, müsste jedes dieser Probleme selbst lösen.

Ein Datenbankmanagementsystem löst diese Probleme ein für alle Mal, für alle. Anwendungen, etwa die Web-App eines Shops und ein Berichtstool, fassen die Datendateien nie direkt an. Sie senden Anfragen, geschrieben in einer Sprache namens SQL. Das Datenbankmanagementsystem prüft die Regeln, plant, wie jede Frage beantwortet wird, und verwaltet viele Nutzer gleichzeitig. Es schreibt die Daten in Dateien auf der Festplatte und hält jede Änderung der Reihe nach in einem Log fest. Und Kopien von beidem gehen regelmäßig an einen Backup-Speicher an einem getrennten Ort.

Die am weitesten verbreitete Art ist die relationale Datenbank. Die Daten liegen in Tabellen. Jede Tabelle beschreibt eine Art von Ding. Jede Zeile ist eine Instanz, etwa ein Kunde, und jede Spalte ist ein Fakt darüber, etwa die E-Mail-Adresse. Jede Tabelle hat einen Primärschlüssel, einen Wert, der für jede Zeile eindeutig ist, etwa die Kunden-ID. Auch eine Bestellung enthält eine Kunden-ID. Das ist ein Fremdschlüssel: Er verweist auf eine Zeile in der Kundentabelle. Die Datenbank weist eine Bestellung für einen Kunden zurück, den es nicht gibt. Jeder Fakt wird nur einmal gespeichert. Eine Bestellung kann viele Produkte enthalten, daher hält eine Verknüpfungstabelle, die Bestellpositionen, eine Zeile pro Produkt und Bestellung fest. Um eine Frage zu beantworten, muss man dann Tabellen kombinieren, und das erledigt SQL mit einem Join.

Wie findet die Datenbank Daten schnell? Angenommen, eine Bestelltabelle hat zehn Millionen Zeilen, und eine Support-Ansicht braucht die zwanzig Bestellungen eines Kunden. Ohne Hilfe muss die Datenbank jede einzelne Zeile lesen und prüfen. Das nennt man einen Full Table Scan. Ein Index funktioniert wie das Register hinten in einem Buch. Er ist ein sortierter Baum, nur drei oder vier Ebenen tief, daher liest die Datenbank eine Handvoll Seiten und geht direkt zu den zwanzig Zeilen. Wächst die Tabelle auf das Zehnfache, leistet der Scan die zehnfache Arbeit. Der Index kommt höchstens um eine Ebene dazu. Aber Indizes sind nicht kostenlos. Jede neue Bestellung muss auch den Index aktualisieren, und er belegt Platz. Es ist wieder der Tausch von Platz gegen Geschwindigkeit.

Um hundert Pfund von Alice zu Bob zu überweisen, braucht es zwei Änderungen. Stürzt der Server dazwischen ab, ist das Geld verschwunden. Eine Transaktion lässt Änderungen gemeinsam gelingen oder gar nicht. Ihre Garantien nennt man ACID. Atomarität bedeutet alles oder nichts. Konsistenz bedeutet, dass die Regeln nie verletzt bleiben. Eine Regel, dass Kontostände nicht unter null fallen dürfen, weist eine Überweisung zurück, die Alice sich nicht leisten kann. Isolation bedeutet, dass sich zwei gleichzeitige Überweisungen nicht beide auf denselben alten Kontostand stützen können. Und Dauerhaftigkeit bedeutet: Wenn die App „Überweisung abgeschlossen“ meldet, ist die Änderung wirklich gespeichert und übersteht einen Absturz oder Stromausfall.

Relationale Datenbanken sind nicht die einzige Option. NoSQL ist ein Sammelbegriff für Datenbanken, die Tabellen nicht auf dieselbe Weise nutzen. Dokumentendatenbanken speichern in sich geschlossene Datensätze, was etwa zu einem Produktkatalog passt. Key-Value-Stores sind riesige Wörterbücher für sehr schnelle Abfragen, etwa für Warenkörbe. Wide-Column-Stores bewältigen riesige Mengen an Schreibvorgängen, etwa Sensormesswerte, verteilt über viele Maschinen. Und Graphdatenbanken beantworten Fragen nach Verbindungen. Viele reale Systeme nutzen mehr als eine Art, jede dort, wo sie passt.

Festplatten fallen aus, und Menschen machen Fehler, daher brauchen Daten Backups an einem getrennten Ort. Und ein Backup, das nie wiederhergestellt wurde, ist eine Hoffnung, kein Backup. Ein Replikat, eine zweite Datenbank, die die erste kopiert, schützt vor einer ausgefallenen Maschine. Aber ein falsches Löschen erreicht das Replikat innerhalb von Millisekunden. Und schließlich überdauern Daten meist den Code um sie herum. Anwendungen werden alle paar Jahre neu geschrieben; Kundendaten werden über Jahrzehnte aufbewahrt. Deshalb gehören Entscheidungen darüber, wo Daten liegen, zu den am teuersten rückgängig zu machenden.

Der vollständige Abschnitt enthält echte SQL-Abfragen, eine funktionierende Banküberweisung und mehr zum Schema des Shops. Als Nächstes, in Abschnitt null Punkt sechs, geht es um APIs und darum, wie getrennte Systeme zusammenarbeiten.

Was Sie wissen müssen

Warum Programme einen Ort für ihre Daten brauchen

Ein laufendes Programm hält seine Arbeitsdaten im Arbeitsspeicher (RAM), der schnell ist, aber alles vergisst, sobald das Programm endet oder die Maschine neu startet (siehe Was Software ist). Alles, was einen Neustart überstehen muss, etwa ein Kundenkonto, eine Bestellung oder ein Krankenhaustermin, muss auf einen Speicher geschrieben werden, der seinen Inhalt ohne Strom behält: eine Festplatte oder SSD. Daten, die auf diese Weise erhalten bleiben, nennt man persistent, und die Entscheidung, wie man sie persistiert, gehört zu den ersten und langlebigsten Entscheidungen in jedem System.

Die einfachste Art, Daten zu persistieren, ist eine Datei: eine benannte Folge von Bytes, die das Betriebssystem auf der Festplatte ablegt. Dateien eignen sich hervorragend für alles, was von Natur aus ein ganzes Dokument ist: Bilder, PDFs, Videos, Konfiguration, Logs, exportierte Berichte. Systeme legen sie oft in einem Object Storage in der Cloud ab (einem Dienst, der Dateien unter einem Namen speichert) und speichern anderswo nur einen Verweis.

Mühsam werden Dateien, wenn viele Nutzer gleichzeitig kleine Teile strukturierter Daten lesen und ändern. Stellen Sie sich einen Shop vor, der alle Bestellungen in einer einzigen Tabellenkalkulationsdatei führt. Zwei Kunden schließen im selben Moment den Bezahlvorgang ab: Wessen Änderung gewinnt? Der Server stürzt mitten beim Speichern ab: Ist die Datei nun halb alt, halb neu? Ein Supportmitarbeiter will die Bestellungen eines Kunden sehen: Muss das Programm die ganze Datei lesen? Jemand tippt eine Kundennummer ein, die es nicht gibt: Wer hält ihn auf? Jedes Programm, das die Datei nutzt, müsste jedes dieser Probleme lösen, und zwar identisch.

Was eine Datenbank ist

Eine Datenbank ist eine organisierte Sammlung von Daten, und das Programm, das sie verwaltet, ist ein Datenbankmanagementsystem (DBMS), etwa PostgreSQL, MySQL, Microsoft SQL Server, Oracle Database oder SQLite. Oft sagt man für beides „die Datenbank“. Das DBMS löst die Probleme mit Dateien ein für alle Mal, für alle: Es lässt viele Nutzer gleichzeitig arbeiten, ohne dass sie sich gegenseitig in die Quere kommen, findet Daten schnell, weist Daten zurück, die gegen die Regeln verstoßen, und kehrt nach einem Absturz in einen korrekten Zustand zurück.

Die Analogie ist eine Bibliothek. Ein Stapel Papiere auf einem Schreibtisch funktioniert, solange Sie ihn allein nutzen. Eine Bibliothek hat einen Katalog, Regeln dafür, wo was steht, eine Aufzeichnung, wer was ausgeliehen hat, und eine Bibliothekarin, die nicht zulässt, dass zwei Personen dasselbe Exemplar nehmen. Anwendungen fassen die Regale nicht an; sie fragen die Bibliothekarin.

In den meisten Geschäftssystemen läuft das DBMS als eigener Serverprozess, oft auf einer eigenen Maschine, und Anwendungen sprechen über das Netzwerk mit ihm (siehe Wie Computer kommunizieren). SQLite ist eine bekannte Ausnahme: Es ist eine Bibliothek, die innerhalb der Anwendung läuft und die ganze Datenbank in einer einzigen Datei speichert, weshalb man es in Smartphones, Browsern und kleinen Werkzeugen findet.

Wo die Daten tatsächlich liegen

Anwendungen senden SQL an einen DB-Server, dessen DBMS Datendateien und ein Log auf Festplatte schreibt, die in getrennten Backup-Speicher kopiert werdenDatenbankserver-MaschineShop-Web-Appliest und schreibtBerichtstoolliest nurSQLSQLDBMSprüft Regelnplant Abfragenverwaltet TransaktionenDatendateienTabellen und IndizesTransaktionslogalle Änderungen, geordnetBackup-Speichergetrennter Ortregelm. Kopien
Anwendungen schreiben nie direkt in die Dateien der Datenbank: Sie senden Anfragen an das DBMS, das Datendateien und ein Log schreibt, und ein separater Prozess kopiert beides in einen Backup-Speicher.

Relationale Datenbanken: Tabellen, Schlüssel und Beziehungen

Die am weitesten verbreitete Art von Datenbank ist die relationale Datenbank, die auf einem Modell beruht, das Edgar F. Codd von IBM 1970 veröffentlichte. Die Daten liegen in Tabellen. Jede Tabelle beschreibt eine Art von Ding (Kunden, Bestellungen, Produkte). Jede Zeile ist eine Instanz dieses Dings (ein Kunde), und jede Spalte ist ein Fakt darüber (Name, E-Mail-Adresse, Beitrittsdatum), mit einem deklarierten Typ wie Ganzzahl, Text, Datum oder Dezimalzahl.

Jede Tabelle hat einen Primärschlüssel: eine Spalte (oder Kombination von Spalten), deren Wert für jede Zeile eindeutig und nie leer ist, etwa customer_id. Ein Fremdschlüssel ist eine Spalte in einer Tabelle, die den Primärschlüssel einer Zeile in einer anderen Tabelle enthält. Eine Bestellung hat eine Spalte customer_id, die angibt, welcher Kunde sie aufgegeben hat. Wird sie als Fremdschlüssel deklariert, weist das DBMS eine Bestellung für Kunde 999 zurück, wenn es keinen Kunden 999 gibt: Das nennt man referenzielle Integrität.

Die Gesamtheit aus Tabellen, Spalten, Typen, Schlüsseln und Regeln ist das Schema. Das Schema ist ein Vertrag: Jedes Programm, das die Datenbank nutzt, kann sich darauf verlassen, dass eine Bestellung immer einen gültigen Kunden und einen Preis hat, der eine Zahl ist. Ein Schema später zu ändern, wenn Millionen von Zeilen und mehrere Programme davon abhängen, ist möglich, erfordert aber Sorgfalt; deshalb hat die Schemaevolution in Kapitel 7 einen eigenen Abschnitt.

Relationale Entwürfe versuchen, jeden Fakt nur einmal zu speichern. Die E-Mail-Adresse eines Kunden steht in einer Zeile der Kundentabelle und wird nicht in jede Bestellung kopiert. Das nennt man Normalisierung. Eine Aktualisierung erfolgt so an einer Stelle und kann keine widersprüchlichen Kopien hinterlassen. Der Preis für diese Ordnung ist, dass man zur Beantwortung einer Frage oft Tabellen kombinieren muss, und genau dafür gibt es Joins.

Fragen stellen mit SQL

SQL (Structured Query Language, ausgesprochen „S-Q-L“ oder „Sequel“) ist die Sprache, mit der man mit relationalen Datenbanken arbeitet. Sie wurde in den 1970er-Jahren bei IBM entwickelt und Ende der 1980er-Jahre zu einem ANSI- und ISO-Standard; jedes große relationale DBMS spricht sie, jeweils mit kleinen Dialektunterschieden. SQL ist deklarativ: Sie beschreiben das gewünschte Ergebnis, nicht die Schritte, um es zu finden. Der Query Planner des DBMS wählt die Schritte, zum Beispiel ob eine ganze Tabelle gelesen oder ein Index genutzt wird.

Ein Join kombiniert Zeilen aus zwei oder mehr Tabellen anhand übereinstimmender Spalten, meist ein Fremdschlüssel gegen den Primärschlüssel, auf den er verweist. Die zweite Abfrage unten listet jede Position der Bestellungen eines Kunden zusammen mit dem Produktnamen auf und greift dabei auf drei Tabellen gleichzeitig zu.

-- One customer's orders since 1 October, newest first
SELECT order_id, status, placed_at
FROM orders
WHERE customer_id = 42
  AND placed_at >= '2026-10-01'
ORDER BY placed_at DESC;

-- Each order line with the product's name (a join)
SELECT o.order_id, p.name, i.quantity, i.unit_price
FROM orders AS o
JOIN order_items AS i ON i.order_id = o.order_id
JOIN products AS p ON p.product_id = i.product_id
WHERE o.customer_id = 42;
Zwei Abfragen auf das Shop-Schema, das weiter unten in diesem Abschnitt gezeigt wird: ein gefiltertes SELECT und ein JOIN über drei Tabellen.

INSERT, UPDATE und DELETE ändern Daten, und CREATE TABLE definiert das Schema. Anwendungscode sendet SQL oft über einen objektrelationalen Mapper (ORM), eine Bibliothek, die Zeilen in Objekte umwandelt. ORMs sparen Tipparbeit, verbergen aber das tatsächlich gesendete SQL; ein klassisches Performance-Problem ist ein ORM, das unbemerkt eine Abfrage pro Zeile statt eines einzigen Joins absetzt.

Indizes: das Register des Buchs, nicht das ganze Buch

Ohne Hilfe kann die Datenbank alle Bestellungen von Kunde 42 nur finden, indem sie jede Zeile der Bestelltabelle liest und prüft: ein Full Table Scan, der in der Notation aus Daten und Algorithmen O(n) ist. Ein Datenbankindex ist eine zusätzliche, sortierte Datenstruktur, die Werte einer Spalte den Zeilen zuordnet, die sie enthalten, so wie das Register hinten in einem Buch Wörter den Seitenzahlen zuordnet. Die meisten relationalen Datenbanken verwenden standardmäßig einen B-Baum, einen breiten, flachen, stets balancierten Baum, der einen Wert in O(log n) Schritten findet.

Primärschlüssel werden automatisch indiziert. Weitere Indizes legt man gezielt für Spalten an, nach denen Abfragen filtern, verknüpfen oder sortieren: CREATE INDEX idx_orders_customer ON orders (customer_id);. Indizes sind nicht kostenlos. Jedes Einfügen, Aktualisieren oder Löschen muss auch jeden Index der Tabelle aktualisieren, und jeder Index belegt Festplattenplatz und Arbeitsspeicher. Eine Tabelle mit fünfzehn Indizes liest schnell und schreibt langsam. Die Wahl der Indizes ist eine Abwägung, die sich an den Abfragen orientiert, die das System tatsächlich ausführt, und damit ein frühes Beispiel für die Abwägung zwischen Platz und Zeit, die sich durch die gesamte Architektur zieht.

Ausgearbeitetes Beispiel

Ausgearbeitetes Beispiel: die Bestellungen eines Kunden finden

Ein Shop hat 10 Millionen Zeilen in seiner Bestelltabelle. Die Kundenservice-Ansicht fragt alle Bestellungen eines Kunden ab, etwa 20 Zeilen.

Ohne IndexFull Table Scan: Die Datenbank liest und prüft alle 10.000.000 Zeilen, um die 20 passenden zu finden.
Mit einem B-Baum-Index auf customer_idJede Indexseite enthält Hunderte von Schlüsseln, daher ist der Baum nur drei oder vier Ebenen tief. Die Datenbank liest eine Handvoll Indexseiten und holt dann die 20 passenden Zeilen.
WachstumWächst die Tabelle auf 100 Millionen Zeilen, leistet der Scan die zehnfache Arbeit; die Indexsuche kommt höchstens um eine Ebene dazu.
Kosten an anderer StelleJede neue Bestellung schreibt nun in die Tabelle und in den Index. Der Festplattenbedarf wächst um die Größe des Index.

Das ist O(n) gegenüber O(log n) aus Abschnitt 0.2: oft der Unterschied zwischen einer Antwort in Sekunden und einer in Millisekunden. Viele Beschwerden über langsame Abfragen in jungen Systemen behebt der richtige Index, nicht größere Hardware.

Transaktionen und ACID: die Banküberweisung

Um 100 Pfund von Alices Konto auf Bobs Konto zu überweisen, braucht es zwei Änderungen: 100 bei Alice abziehen, 100 bei Bob gutschreiben. Stürzt der Server nach der ersten Änderung und vor der zweiten ab, sind 100 Pfund verschwunden. Laufen zwei Überweisungen von Alice gleichzeitig, sehen womöglich beide einen Kontostand von 150 und beide gelingen, sodass ihr Konto überzogen ist. Eine Transaktion fasst mehrere Operationen zu einer Arbeitseinheit zusammen, die entweder vollständig abgeschlossen wird (Commit) oder gar keine Wirkung hat (Rollback).

Die Garantien einer Transaktion werden als ACID zusammengefasst, ein Akronym, das Theo Härder und Andreas Reuter 1983 prägten, aufbauend auf früheren Arbeiten von Jim Gray:

EigenschaftWas sie versprichtBei der Banküberweisung
AtomaritätAlle Änderungen der Transaktion finden statt, oder keine.Alice wird nie belastet, ohne dass Bob die Gutschrift erhält.
KonsistenzEine Transaktion überführt die Datenbank von einem gültigen Zustand in einen anderen; Regeln wie Schlüssel und Prüfungen bleiben nie verletzt.Eine Regel, dass Kontostände nicht unter null fallen dürfen, weist eine Überweisung zurück, die Alice sich nicht leisten kann.
IsolationGleichzeitig laufende Transaktionen sehen die halb fertige Arbeit der anderen nicht; das Ergebnis ist so, als liefen sie nacheinander (in einem Maß, das die Isolationsebene festlegt).Zwei gleichzeitige Überweisungen von Alice können sich nicht beide auf denselben alten Kontostand stützen.
DauerhaftigkeitSobald die Datenbank einen Commit bestätigt, übersteht die Änderung einen Absturz oder Stromausfall.Wenn die App „Überweisung abgeschlossen“ meldet, ist das Geld wirklich bewegt, weil die Änderung bereits im Log auf der Festplatte steht.
import sqlite3

conn = sqlite3.connect("bank.db")
# The accounts table was created with CHECK (balance >= 0)

def transfer(from_id, to_id, amount):
    try:
        with conn:  # one transaction: commit on success, rollback on error
            debit = conn.execute(
                "UPDATE accounts SET balance = balance - ? WHERE id = ?",
                (amount, from_id))
            credit = conn.execute(
                "UPDATE accounts SET balance = balance + ? WHERE id = ?",
                (amount, to_id))
            if debit.rowcount != 1 or credit.rowcount != 1:
                raise ValueError("No such account")  # forces a rollback
        return "Transfer complete"
    except (sqlite3.Error, ValueError):
        return "Transfer refused: no account was changed"
Eine Überweisung mit dem eingebauten Python-Modul sqlite3. Der with-Block führt einen Commit aus, wenn beide Aktualisierungen gelingen, und ein Rollback, wenn eine davon scheitert oder ein Konto nicht existiert.

Beachten Sie die Prüfung von rowcount (der Anzahl der Zeilen, die jede Aktualisierung geändert hat): Eine Aktualisierung, die auf kein Konto passt, ist für die Datenbank kein Fehler, daher muss der Code sie selbst als Fehler behandeln. Zwei weitere Details sind über dieses Beispiel hinaus wichtig. Die Platzhalter ? übergeben Werte getrennt vom SQL-Text; das ist die Standardabwehr gegen SQL-Injection, einen Angriff, bei dem Benutzereingaben als Code in eine Abfrage eingeschleust werden (Kapitel 8 kommt darauf zurück). Und das Versprechen der Dauerhaftigkeit wird durch ein Write-Ahead-Log eingehalten: Das DBMS schreibt jede Änderung in ein nur erweiterbares Log auf der Festplatte, bevor es den Commit bestätigt, sodass es nach einem Absturz Arbeit wiederholen oder rückgängig machen kann, um einen korrekten Zustand zu erreichen.

Isolation ist die subtilste Eigenschaft. Vollständige Isolation (serialisierbar genannt) ist sicher, begrenzt aber, wie viel Arbeit parallel laufen kann; daher bieten Datenbanken schwächere Isolationsebenen, die etwas Schutz gegen Geschwindigkeit tauschen, und viele nutzen standardmäßig eine schwächere Ebene. Welche Anomalien jede Ebene zulässt, behandelt Transaktionen, ACID und Isolationsebenen. Vorerst ist der Kerngedanke, dass eine einzelne Datenbank innerhalb einer Transaktion starke, gut verstandene Garantien gibt und dass diese Garantien viel schwerer zu erreichen sind, sobald Daten über mehrere Datenbanken oder Services verteilt sind.

NoSQL: andere Formen von Daten

Relationale Datenbanken sind nicht die einzige Option. NoSQL ist ein Sammelbegriff, populär geworden durch ein Meetup 2009 in San Francisco, für Datenbanken, die nicht das relationale Modell verwenden. Sie entstanden, um sehr große, über viele Maschinen verteilte Datenmengen, flexible Datenformen und Zugriffsmuster zu bewältigen, die Tabellen nur umständlich bedienen. Der Name sagt, was sie nicht sind, und umfasst daher recht unterschiedliche Familien.

Die vier wichtigsten NoSQL-Familien

KlasseWie sie Daten speichertGut geeignet fürBeispiele
DokumentendatenbankJeder Datensatz ist ein in sich geschlossenes Dokument, meist JSON-ähnlich, das Listen und Unterobjekte verschachteln kann. Dokumente in einer Collection müssen nicht genau dieselben Felder haben.Daten, die als Ganzes gelesen und geschrieben werden: ein Produktkatalog mit unterschiedlichen Attributen, ein Benutzerprofil, Inhalte für eine Webseite.MongoDB, Couchbase, Firestore
Key-Value-StoreEin riesiges Wörterbuch: Ein eindeutiger Schlüssel verweist auf einen Wert, in den die Datenbank nicht hineinschaut. Nachgeschlagen wird nur über den Schlüssel.Sehr schnelle einfache Abfragen: Sessions, Warenkörbe, Caches, Feature-Flags.Redis, Amazon DynamoDB, etcd
Wide-Column-StoreZeilen, die durch einen Schlüssel identifiziert werden, jede mit vielen Spalten, gruppiert und über viele Maschinen verteilt (partitioniert). Tabellen werden um die Abfragen herum entworfen, die ausgeführt werden sollen.Riesige Mengen an Schreibvorgängen, etwa Sensormesswerte, Ereignislogs und Nachrichtenverläufe, über viele Server und Rechenzentren hinweg.Apache Cassandra, Apache HBase, Google Bigtable
GraphdatenbankKnoten (Dinge) und Kanten (Beziehungen), beide mit Eigenschaften, wie in den Graphen aus Abschnitt 0.2.Fragen nach Verbindungen: Freunde von Freunden, Betrugsringe, Routenplanung, Empfehlungen.Neo4j, Amazon Neptune

Fachleute sind sich über den Standard uneinig. Ein Lager sagt, man solle mit einer relationalen Datenbank beginnen, sofern es keinen klaren Grund dagegen gibt: SQL ist weithin bekannt, Transaktionen und Joins sind mächtig, und moderne relationale Systeme speichern auch JSON-Dokumente gut. Das andere Lager sagt, man solle von Anfang an den Speicher wählen, der zum Zugriffsmuster passt, weil ein Dokumenten- oder Key-Value-Store für die richtige Last einfacher sein und günstiger skalieren kann. Beide sind sich einig, dass viele reale Systeme mehr als eine Art nutzen, jede dort, wo sie passt; das nennt man polyglotte Persistenz. Der Abschnitt Eine Datenbank wählen in Kapitel 7 bietet einen strukturierten Weg zur Entscheidung.

Ausgearbeitetes Beispiel: das Schema eines Onlineshops

Nehmen Sie einen kleinen Onlineshop. Er muss wissen, wer seine Kunden sind, was er verkauft und wer was bestellt hat. Ein erster Versuch könnte alles in eine Tabelle packen, mit einer Zeile pro Bestellung und den Spalten product1, product2, product3. Das scheitert, sobald jemand vier Dinge bestellt, und es kopiert Name und Adresse des Kunden in jede Bestellung.

Schema eines Onlineshops: Bestellungen verweisen auf Kunden, Bestellpositionen auf Bestellungen und Produktecustomerscustomer_id (PK)nameemailcreated_atordersorder_id (PK)customer_id (FK)statusplaced_atorder_itemsorder_id (PK, FK)product_id (PK, FK)quantityunit_priceproductsproduct_id (PK)namepricestockJeder Pfeil führt vom Fremdschlüssel (FK) zum Primärschlüssel (PK), auf den er verweist.
Ein normalisiertes Shop-Schema: Jeder Fakt wird einmal gespeichert, und Fremdschlüssel halten die Beziehungen fest.

Ausgearbeitetes Beispiel

Ausgearbeitetes Beispiel: Warum das Schema vier Tabellen hat

Das Schema oben beantwortet die Fragen des Shops, ohne Fakten zu kopieren.

customersEine Zeile pro Person. Eine geänderte E-Mail-Adresse ändert eine Zeile.
ordersEine Zeile pro Bezahlvorgang. customer_id ist ein Fremdschlüssel, daher gehört jede Bestellung zu einem echten Kunden. Ein Kunde hat viele Bestellungen: eine 1:n-Beziehung.
productsEine Zeile pro Artikel im Angebot, mit aktuellem Preis und Lagerbestand.
order_itemsEine Bestellung enthält viele Produkte, und ein Produkt kommt in vielen Bestellungen vor: eine n:m-Beziehung. Relationale Datenbanken bilden sie mit einer Verknüpfungstabelle ab, mit einer Zeile pro Produkt und Bestellung.
Bewusste Kopie: unit_priceDer Preis wird beim Aufgeben der Bestellung in order_items kopiert. Das ist kein Normalisierungsfehler: Die Bestellung muss festhalten, was der Kunde tatsächlich bezahlt hat, auch wenn sich der Katalogpreis nächste Woche ändert.
Eine Bestellung aufgebenDas Einfügen der Bestellung, das Einfügen ihrer Positionen und das Verringern des Lagerbestands jedes Produkts geschehen in einer Transaktion, sodass ein Absturz nie eine Bestellung ohne Positionen oder einen nicht verringerten Bestand hinterlassen kann.

Guter Schemaentwurf beginnt bei den Fragen, die das Unternehmen stellt, und den Fakten, die es nie verlieren darf, nicht bei den Bildschirmen der ersten Version der App.

Derselbe Shop könnte in einer Dokumentendatenbank jede Bestellung als ein Dokument speichern, mit den Positionen darin verschachtelt; das passt gut, um eine ganze Bestellung auf einmal zu lesen, erschwert aber Fragen wie „Gesamtumsatz pro Produkt“.

Backups, Wiederherstellung und Managed Databases

Festplatten fallen aus, Menschen führen das falsche DELETE aus, Software hat Bugs, und Angreifer verschlüsseln Daten, um Lösegeld zu erpressen. Ein Backup ist eine Kopie der Daten, die getrennt aufbewahrt wird und aus der die Datenbank wiederhergestellt werden kann. Drei professionelle Gewohnheiten zählen. Ein Backup, das nie wiederhergestellt wurde, ist eine Hoffnung, kein Backup; deshalb proben Teams Wiederherstellungen und messen deren Dauer. Kopien werden an mehr als einem Ort aufbewahrt (eine verbreitete Faustregel lautet: drei Kopien, auf zwei Arten von Medien, eine davon extern). Und ein Replikat, eine zweite Datenbank, die die erste fortlaufend kopiert, schützt vor dem Ausfall einer Maschine, aber nicht vor Fehlern: Ein falsches DELETE erreicht das Replikat innerhalb von Millisekunden. Viele Datenbanken können ein regelmäßiges Vollbackup mit dem gespeicherten Transaktionslog kombinieren, um auf jeden beliebigen Zeitpunkt wiederherzustellen; das nennt man Point-in-Time-Recovery.

Eine Datenbank gut zu betreiben (Patches einspielen, Tuning, Replikation, Backups und Wiederherstellung um drei Uhr morgens) ist anspruchsvolle, nie endende Arbeit; deshalb verkaufen Cloud-Anbieter eine Managed Database: Amazon RDS, Azure SQL Database und Google Cloud SQL sind Beispiele. Der Anbieter kümmert sich um die Maschinen, Software-Updates, automatische Backups und das Failover auf eine Standby-Kopie; beim Kunden bleiben weiterhin das Schema, die Abfragen, die Indizes, die Zugriffsrechte und die Kosten. Managed Services werden in Server und die Cloud allgemeiner behandelt.

Warum das für die Architektur wichtig ist

Daten überdauern meist den Code um sie herum: Anwendungen werden alle paar Jahre neu geschrieben, während Kundendaten und Krankengeschichten über Jahrzehnte aufbewahrt werden. Entscheidungen darüber, wo Daten liegen, welche Art von Datenbank sie hält, welches Programm für sie zuständig ist und welche Garantien sie brauchen, gehören daher zu den am teuersten rückgängig zu machenden. Mehrere große Architekturfragen erwachsen direkt aus diesem Abschnitt: ob jeder Service seine eigene Datenbank besitzen sollte (Datenhoheit), welche Datenbank zu welcher Aufgabe passt (Eine Datenbank wählen) und wie man Daten korrekt hält, wenn eine geschäftliche Aktion mehrere Datenbanken berührt und keine einzelne ACID-Transaktion mehr verfügbar ist (Sagas). Wenn ein Architekt fragt: „Wo liegt dafür die Source of Truth?“, dann ist dieser Abschnitt der Grund.

Prüfen Sie Ihr Verständnis

Warum ist eine Tabellenkalkulationsdatei ein schlechter Ort für die Bestellungen eines Onlineshops, sobald viele Kunden gleichzeitig einkaufen?

Gleichzeitige Schreiber können sich gegenseitig überschreiben, ein Absturz mitten beim Speichern kann die Datei beschädigen, um die Bestellungen eines Kunden zu finden, muss alles gelesen werden, und nichts setzt Regeln durch, etwa dass jede Bestellung einen echten Kunden hat. Ein DBMS löst alle vier Probleme.

Was macht im Shop-Schema customer_id in der Tabelle orders zu einem Fremdschlüssel, und was bringt Ihnen das?

Die Spalte enthält den Primärschlüssel einer Zeile in der Tabelle customers und ist als Verweis darauf deklariert. Die Datenbank weist dann Bestellungen für Kunden zurück, die es nicht gibt.

Warum nicht einfach jede Spalte jeder Tabelle indizieren?

Jeder Index muss bei jedem Einfügen, Aktualisieren und Löschen aktualisiert werden und belegt Platz. Zu viele Indizes machen Schreibvorgänge langsam und Speicher teuer; deshalb wählt man Indizes passend zu den tatsächlich ausgeführten Abfragen.

Welche ACID-Eigenschaft ist verletzt, wenn ein Absturz Alice belastet, Bob aber keine Gutschrift erhalten hat?

Atomarität: Die beiden Änderungen hätten gemeinsam oder gar nicht stattfinden müssen.

Ein Team sagt: „Wir haben ein Replikat in einem anderen Rechenzentrum, also brauchen wir keine Backups.“ Was hat es übersehen?

Ein Replikat kopiert Fehler und beschädigte Daten genauso getreu wie gute Daten, sodass ein versehentliches Löschen oder ein Ransomware-Angriff es binnen Augenblicken erreicht. Backups, idealerweise mit Point-in-Time-Recovery, schützen davor.

Weiter · 0.6APIs und IntegrationWas eine API ist und warum sie ein Vertrag ist, wie REST-APIs URLs, HTTP-Verben, Statuscodes und JSON nutzen und wie Schlüssel, Webhooks, Queues, Versionen und Rate Limits die Integration prägen.Weiter →

Quellen und weiterführende Literatur

Haben Sie eine Frage zu diesem Abschnitt? ArchitectBot, der KI-Lernassistent des Kurses, antwortet zuerst aus dem Kurs.

Fragen: „Wie entscheide ich mich für einen kleinen Onlineshop zwischen einer relationalen Datenbank wie PostgreSQL und einer Dokumentendatenbank wie MongoDB?“