Datentypen richtig wählen: Speicherplatz und Performance sparen
AI generated
InnoDB
SQL
MySQL · Datenbankdesign · Schema-Optimierung
Datentypen richtig wählen
Speicherplatz und Performance sparen

Ein zu breiter INT, ein VARCHAR-Feld, das eigentlich TEXT sein sollte, oder FLOAT statt DECIMAL für Geldbeträge summieren sich in großen Tabellen zu erheblichem, oft übersehenem Speicherplatzbedarf und Performance-Verlust. Die Wahl des richtigen Datentyps ist eine der günstigsten Optimierungen im gesamten Datenbankdesign. Dieser Artikel erklärt die wichtigsten Datentyp-Entscheidungen mit konkreten Sizing-Regeln.

18 Min. Lesezeit INT · VARCHAR · DECIMAL · ENUM MySQL 8.0 · MariaDB 10.x

1. Warum die Datentyp-Wahl so viel bewirkt

Die Wahl des richtigen Datentyps wirkt sich auf drei Ebenen gleichzeitig aus: Speicherplatz auf der Platte, Speicherverbrauch im Buffer Pool und Abfrageperformance. Ein zu großzügig gewählter Datentyp verschwendet nicht nur Plattenplatz, sondern belegt auch mehr Platz im ohnehin begrenzten Buffer Pool, wodurch weniger Daten gleichzeitig im schnellen RAM-Cache vorgehalten werden können. Bei einer Tabelle mit wenigen tausend Zeilen ist der Effekt vernachlässigbar, bei einer Tabelle mit hundert Millionen Zeilen kann derselbe Fehler mehrere Gigabyte verschwendeten Speicher bedeuten.

Ein zweiter, oft unterschätzter Effekt betrifft Indizes: Jeder zusätzliche Byte in einer indizierten Spalte wird nicht nur einmal, sondern für jeden Indexeintrag gespeichert, was den Effekt bei mehrfach indizierten Spalten vervielfacht. Die richtige Datentyp-Wahl ist deshalb keine kosmetische Feinheit, sondern eine der wenigen Optimierungen, die sich ohne Anwendungscode-Änderung, ohne zusätzliche Hardware und ohne architektonischen Umbau direkt im Schema umsetzen lässt.

2. Integer-Typen richtig dimensionieren

MySQL bietet fünf Integer-Typen unterschiedlicher Größe: TINYINT mit 1 Byte, SMALLINT mit 2 Byte, MEDIUMINT mit 3 Byte, INT mit 4 Byte und BIGINT mit 8 Byte. Ein häufiger Fehler ist, praktisch jede numerische Spalte reflexartig als INT anzulegen, selbst wenn der Wertebereich das gar nicht erfordert. Ein Statusfeld mit fünf möglichen Werten braucht kein INT mit einem Bereich bis über zwei Milliarden, ein TINYINT mit einem Bereich bis 255 reicht völlig aus und spart drei Byte pro Zeile.

Bei Primärschlüsseln lohnt sich besondere Sorgfalt, weil der Primärschlüssel bei InnoDB als clustered Index physisch die gesamte Tabelle sortiert und zusätzlich in jedem Sekundärindex als Referenz mitgespeichert wird. Ein BIGINT-Primärschlüssel statt eines ausreichenden INT UNSIGNED verdoppelt den Speicherbedarf jedes Sekundärindex-Eintrags. Die Faustregel: Für Tabellen mit absehbar unter 4 Milliarden Zeilen reicht INT UNSIGNED, für alles darüber, etwa sehr große Log-Tabellen, ist BIGINT UNSIGNED notwendig.


-- Compare storage impact of integer types on a primary key
CREATE TABLE order_status_correct (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,      -- 4 bytes, up to 4.29 billion
    status TINYINT UNSIGNED NOT NULL,                -- 1 byte, up to 255 values
    order_id INT UNSIGNED NOT NULL,                  -- 4 bytes, matches parent key type
    created_at DATETIME NOT NULL
) ENGINE=InnoDB;

-- Oversized, wastes space in every secondary index reference
CREATE TABLE order_status_wasteful (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,             -- 8 bytes, rarely needed
    status INT NOT NULL,                               -- 4 bytes for 5 possible values
    order_id BIGINT NOT NULL,
    created_at DATETIME NOT NULL
) ENGINE=InnoDB;

-- Check actual value range before choosing a type
SELECT MIN(status), MAX(status), COUNT(DISTINCT status) FROM order_status_correct;

3. VARCHAR vs. TEXT: wann welcher Typ

VARCHAR(n) speichert eine Zeichenkette variabler Länge bis zu n Zeichen direkt in der Datenzeile, zusammen mit einem Längenpräfix von 1 oder 2 Byte, je nachdem ob n über 255 liegt. TEXT hingegen wird von InnoDB unter bestimmten Bedingungen als sogenannte Off-Page-Spalte behandelt, bei der nur ein Verweis in der eigentlichen Zeile liegt und der tatsächliche Inhalt in separaten Overflow-Pages gespeichert wird. Das hat direkte Konsequenzen für Performance: Ein SELECT, der viele TEXT-Spalten ausliest, erzeugt potenziell zusätzliche Seitenzugriffe, während kurze VARCHAR-Felder in derselben Page wie der Rest der Zeile liegen.

Die praktische Regel: Für Felder mit einer bekannten, begrenzten Maximallänge, etwa E-Mail-Adressen, Produktnamen oder SKUs, ist VARCHAR mit einer realistischen Längenbegrenzung fast immer die richtige Wahl. TEXT eignet sich für tatsächlich unbegrenzte Inhalte wie Produktbeschreibungen, Blogartikel-Inhalte oder Freitext-Kommentare. Ein häufiger Anti-Pattern ist VARCHAR(255) als pauschale Standardlänge für jedes Textfeld, unabhängig vom tatsächlichen Inhalt, was weder Speicherplatz spart noch die Datenintegrität durch sinnvolle Längenbegrenzung verbessert.


-- Realistic column sizing based on actual content constraints
CREATE TABLE products (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    sku VARCHAR(32) NOT NULL,               -- bounded, known max length
    name VARCHAR(150) NOT NULL,             -- bounded, realistic product name length
    short_description VARCHAR(500),         -- bounded, still fits in-row usually
    description TEXT,                       -- unbounded, potentially large content
    UNIQUE KEY uq_sku (sku)
) ENGINE=InnoDB;

-- Find columns with unrealistic VARCHAR lengths in an existing schema
SELECT table_name, column_name, character_maximum_length
FROM information_schema.columns
WHERE table_schema = 'shop_db'
  AND data_type = 'varchar'
  AND character_maximum_length >= 255
ORDER BY character_maximum_length DESC;

4. DECIMAL vs. FLOAT für Geldbeträge

Für Geldbeträge ist DECIMAL(m,d) der einzig korrekte Datentyp, niemals FLOAT oder DOUBLE. FLOAT und DOUBLE sind binäre Gleitkommatypen, die bestimmte Dezimalzahlen nicht exakt darstellen können, ähnlich wie sich ein Drittel nicht exakt als endliche Dezimalzahl schreiben lässt. Bei Berechnungen mit Geldbeträgen führt das zu winzigen Rundungsfehlern, die sich über viele Transaktionen hinweg zu spürbaren Differenzen summieren können, ein Problem, das in Buchhaltungssystemen und Zahlungsabwicklung völlig inakzeptabel ist.

DECIMAL speichert Zahlen hingegen als exakte dezimale Darstellung, definiert über Gesamtstellen (m) und Nachkommastellen (d). Für die meisten Währungen reicht DECIMAL(10,2) für Beträge bis 99.999.999,99, wobei bei Kryptowährungen oder Anwendungen mit sehr kleinen Bruchteilen mehr Nachkommastellen nötig sein können. Der Speicherbedarf von DECIMAL ist etwas höher als bei FLOAT, aber dieser geringe Mehraufwand ist für korrekte Finanzberechnungen jede zusätzliche Byte wert.


-- Correct: exact decimal representation for money
CREATE TABLE invoices (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    amount_net DECIMAL(10,2) NOT NULL,
    tax_rate DECIMAL(4,2) NOT NULL,
    amount_gross DECIMAL(10,2) NOT NULL
) ENGINE=InnoDB;

-- Demonstrate the rounding problem with FLOAT
-- SELECT 0.1 + 0.2;                    -- exact with DECIMAL: 0.30
-- With FLOAT/DOUBLE this can yield 0.30000000000000004 internally,
-- causing mismatches in financial reconciliation over many rows

5. ENUM: Vor- und Nachteile im Detail

ENUM speichert intern nicht den Textwert, sondern einen kompakten numerischen Index, der auf die im Schema definierte Werteliste verweist. Bei bis zu 255 möglichen Werten belegt ENUM nur 1 Byte, was deutlich kompakter ist als ein entsprechendes VARCHAR-Feld. Für Felder mit einer kleinen, stabilen Werteliste, etwa einem Bestellstatus mit den Werten neu, bezahlt, versandt und storniert, ist ENUM deshalb sowohl speicherplatzsparend als auch selbstdokumentierend, weil die erlaubten Werte direkt im Schema sichtbar sind.

Der entscheidende Nachteil von ENUM zeigt sich bei Änderungen: Ein neuer Wert erfordert einen ALTER TABLE-Befehl, der bei großen Tabellen und bestimmten Werteänderungen eine vollständige Tabellenumkopie auslösen kann, statt einer schnellen Metadatenänderung. Zudem lässt sich die Werteliste eines ENUM nicht bequem aus einer anderen Tabelle referenzieren, was bei häufig wechselnden oder anwendungsseitig verwalteten Statuswerten zu einer separaten Lookup-Tabelle mit Fremdschlüssel als flexiblerer Alternative führt.


-- ENUM: compact and self-documenting for a small, stable value set
CREATE TABLE orders (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    status ENUM('new', 'paid', 'shipped', 'cancelled') NOT NULL DEFAULT 'new'
) ENGINE=InnoDB;

-- Adding a new value requires an ALTER TABLE, potentially a table rebuild
ALTER TABLE orders MODIFY status
    ENUM('new', 'paid', 'shipped', 'cancelled', 'refunded') NOT NULL DEFAULT 'new';

-- More flexible alternative for frequently changing status values
CREATE TABLE order_statuses (
    id TINYINT UNSIGNED PRIMARY KEY,
    code VARCHAR(20) NOT NULL UNIQUE
) ENGINE=InnoDB;

6. Datum und Zeit: DATE, DATETIME, TIMESTAMP

MySQL bietet mehrere Datentypen für Zeitangaben, die häufig verwechselt werden. DATE speichert nur ein Datum ohne Uhrzeit und belegt 3 Byte, ideal für Geburtsdaten oder Liefertermine ohne Uhrzeitkomponente. DATETIME speichert Datum und Uhrzeit ohne Zeitzonenbezug in 5 Byte plus optionaler Bruchteil-Sekunden-Genauigkeit, wobei der gespeicherte Wert unverändert bleibt, unabhängig von der Session-Zeitzone. TIMESTAMP hingegen speichert intern UTC und rechnet beim Lesen und Schreiben automatisch in die Session-Zeitzone um, belegt dabei aber nur 4 Byte und ist damit kompakter als DATETIME.

Der praktisch relevante Unterschied liegt im Wertebereich und im Zeitzonenverhalten: TIMESTAMP ist auf den Bereich von 1970 bis 2038 begrenzt, was für die meisten Anwendungsfälle ausreicht, aber bei historischen Daten oder weit in der Zukunft liegenden Terminen problematisch werden kann. Für zeitzonenübergreifende Anwendungen mit Nutzern in unterschiedlichen Regionen ist TIMESTAMP meist die bessere Wahl, weil die automatische Umrechnung Fehler durch manuelle Zeitzonenkonvertierung im Anwendungscode vermeidet.


-- Choosing the right temporal type per use case
CREATE TABLE events (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    event_date DATE NOT NULL,                    -- date only, 3 bytes
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  -- UTC-based, 4 bytes
    scheduled_for DATETIME NOT NULL               -- no timezone conversion, 5 bytes
) ENGINE=InnoDB;

7. NULL vs. NOT NULL: Auswirkungen auf Speicher und Index

Jede nullable Spalte einer Tabelle benötigt ein zusätzliches Bit im sogenannten Null-Bitmap-Bereich der Zeile, der Speicherbedarf ist damit gering, aber nicht null. Der eigentlich relevante Effekt von NULL zeigt sich beim Query-Optimizer und bei Indizes: Vergleiche gegen NULL erfordern die explizite Syntax IS NULL oder IS NOT NULL, da normale Vergleichsoperatoren mit NULL immer UNKNOWN statt TRUE oder FALSE zurückgeben, was in WHERE-Klauseln zu unerwarteten leeren Ergebnismengen führen kann, wenn Entwickler das übersehen.

Als generelle Praxis-Empfehlung gilt: Spalten sollten nur dann als nullable definiert werden, wenn das Fehlen eines Werts fachlich tatsächlich eine gültige, unterscheidbare Bedeutung hat, etwa ein optionales Lieferdatum, das noch nicht feststeht. Für Spalten, die eigentlich immer einen Wert haben sollten, ist NOT NULL mit einem sinnvollen DEFAULT-Wert die robustere Wahl, weil sie fehlerhafte Anwendungslogik früher als Constraint-Verletzung statt als stille Dateninkonsistenz aufdeckt.


-- Find nullable columns that might not need to be nullable
SELECT table_name, column_name, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'shop_db'
  AND is_nullable = 'YES'
ORDER BY table_name, column_name;

-- Common trap: NULL comparisons never match with standard operators
-- SELECT * FROM orders WHERE cancelled_at = NULL;   -- always empty result
-- SELECT * FROM orders WHERE cancelled_at IS NULL;  -- correct

8. Praktische Sizing-Regeln und Bestandsanalyse

Für neue Tabellen empfiehlt sich eine kleine Checkliste: Integer-Spalten anhand des tatsächlichen Wertebereichs wählen, nicht reflexartig INT verwenden. VARCHAR-Längen an realistischen Geschäftsregeln orientieren, nicht pauschal auf 255 setzen. Geldbeträge ausnahmslos als DECIMAL speichern. ENUM nur für wirklich stabile, kleine Wertelisten verwenden. Zeittypen entsprechend ihrem tatsächlichen Anwendungsfall wählen, nicht standardmäßig DATETIME für alles.

Für bestehende Datenbanken lohnt sich eine regelmäßige Bestandsanalyse über information_schema, um die größten Tabellen und die darin verwendeten Datentypen zu identifizieren. Häufig zeigt eine solche Analyse, dass ein Großteil des Speicherverbrauchs auf wenige Tabellen mit suboptimalen Datentyp-Entscheidungen zurückzuführen ist, deren Korrektur einen überproportional großen Effekt auf Gesamtspeicherverbrauch und Buffer-Pool-Effizienz hat.


-- Identify the largest tables as candidates for a data type audit
SELECT
    table_name,
    ROUND(data_length / 1024 / 1024, 2) AS data_mb,
    ROUND(index_length / 1024 / 1024, 2) AS index_mb,
    table_rows
FROM information_schema.tables
WHERE table_schema = 'shop_db'
ORDER BY (data_length + index_length) DESC
LIMIT 10;

9. Datentypen im direkten Vergleich

Die folgende Tabelle fasst die wichtigsten Datentypen nach Speicherbedarf und typischem Einsatzzweck zusammen.

Datentyp Speicherbedarf Typischer Einsatz Häufiger Fehler
TINYINT 1 Byte Statusfelder, Flags, kleine Zähler INT statt TINYINT für 5 Statuswerte
VARCHAR(n) n+1 oder n+2 Byte Namen, SKUs, kurze Texte Pauschal VARCHAR(255) für alles
DECIMAL(m,d) Variabel nach m,d Geldbeträge, exakte Berechnungen FLOAT statt DECIMAL für Geld
ENUM 1 bis 2 Byte Kleine, stabile Wertelisten ENUM für häufig wechselnde Werte
TIMESTAMP 4 Byte Zeitzonenbewusste Zeitstempel TIMESTAMP für Daten nach 2038

Keiner dieser Datentypen ist per se falsch, entscheidend ist die bewusste Wahl anhand des tatsächlichen Wertebereichs und Anwendungsfalls, statt einer reflexartigen Standardentscheidung, die für jede Spalte gleichermaßen angewendet wird.

Mironsoft

Schema-Reviews und Datenbankdesign-Beratung

Wie viel Speicherplatz verschwenden eure Datentypen?

Wir analysieren euer Schema, identifizieren überdimensionierte Spaltentypen und liefern eine priorisierte Liste konkreter Anpassungen mit messbarem Effekt auf Speicherverbrauch und Buffer-Pool-Effizienz.

Schema-Audit

Alle Spaltentypen gegen tatsächliche Wertebereiche prüfen

Migrationsplan

Sichere Reihenfolge für Datentyp-Anpassungen ohne Downtime

Design-Guidelines

Verbindliche Datentyp-Standards für neue Tabellen im Team

10. Zusammenfassung

Die Wahl des richtigen Datentyps ist eine der wirkungsvollsten und gleichzeitig günstigsten Optimierungen im Datenbankdesign. Integer-Typen sollten anhand des tatsächlichen Wertebereichs gewählt werden, nicht reflexartig als INT. VARCHAR mit realistischer Längenbegrenzung ist für begrenzte Textfelder die richtige Wahl, TEXT für tatsächlich unbegrenzte Inhalte. Geldbeträge gehören ausnahmslos in DECIMAL-Spalten, niemals in FLOAT oder DOUBLE.

ENUM spart Speicher bei kleinen, stabilen Wertelisten, wird aber unflexibel bei häufigen Änderungen. Zeittypen sollten entsprechend ihrem tatsächlichen Zeitzonenbedarf und Wertebereich gewählt werden. Eine regelmäßige Bestandsanalyse über information_schema deckt zuverlässig auf, wo suboptimale Datentyp-Entscheidungen unnötig Speicherplatz und Buffer-Pool-Kapazität binden.

Datentypen richtig wählen, das Wichtigste auf einen Blick

Integer

TINYINT bis BIGINT anhand des tatsächlichen Wertebereichs wählen, besonders bei Primärschlüsseln.

Text

VARCHAR mit realistischer Länge für begrenzte Felder, TEXT nur für wirklich unbegrenzten Inhalt.

Geld

Immer DECIMAL für exakte Berechnungen, niemals FLOAT oder DOUBLE für Geldbeträge.

Analyse

information_schema.tables und .columns regelmäßig auf Optimierungspotenzial prüfen.

11. FAQ: MySQL Datentypen richtig wählen

1Warum nicht überall INT?
INT belegt immer 4 Byte. TINYINT mit 1 Byte reicht für kleine Statusfelder und spart bei großen Tabellen erheblich Speicher.
2VARCHAR oder TEXT?
VARCHAR mit realistischer Länge für bekannte Maximallängen wie Namen. TEXT für tatsächlich unbegrenzte Inhalte.
3Warum ist FLOAT für Geld gefährlich?
Binäre Gleitkommatypen können bestimmte Dezimalzahlen nicht exakt darstellen, was zu Rundungsfehlern führt.
4Richtiger Typ für Geld?
DECIMAL(m,d) speichert exakte dezimale Werte. DECIMAL(10,2) reicht für die meisten Währungen.
5Nachteile von ENUM?
Neue Werte erfordern ALTER TABLE, potenziell mit Tabellenumkopie. Werteliste nicht extern referenzierbar.
6DATETIME vs. TIMESTAMP?
DATETIME ohne Zeitzone, 5 Byte. TIMESTAMP mit automatischer UTC-Umrechnung, 4 Byte, begrenzt bis 2038.
7Wie viel spart TINYINT?
3 Byte pro Zeile plus zusätzliche Einsparungen in jedem Index, der die Spalte enthält.
8Sollte jede Spalte NOT NULL sein?
Nein, nur wenn ein fehlender Wert fachlich keine gültige eigene Bedeutung hat.
9Überdimensionierte Spalten finden?
Mit information_schema.columns und .tables, größte Tabellen zuerst analysieren.
10Warum kleiner Primärschlüssel?
Wird in jedem Sekundärindex als Referenz mitgespeichert, ein zu großer Typ vervielfacht den Speicherbedarf.