Datentyp-Wahl mit langfristigen Folgen: worauf es ankommt
AI generated
SELECT
JOIN
SQL · Datenmodellierung · Datenbankdesign
Datentyp-Wahl mit langfristigen Folgen
worauf es bei IDs, Text, Geld und Zeit wirklich ankommt

Der Datentyp einer Spalte lässt sich in einer wachsenden Tabelle nur mit erheblichem Aufwand ändern, weshalb die erste Entscheidung besonders sorgfältig getroffen werden sollte. Dieser Beitrag behandelt INT gegen BIGINT bei Primärschlüsseln, realistische VARCHAR-Längen, DECIMAL statt FLOAT für Geldbeträge und die häufigsten Zeitzonen-Fallstricke bei Timestamps, mit konkretem SQL für jede Entscheidung.

16 Min. Lesezeit INT · BIGINT · DECIMAL · TIMESTAMP Standard-SQL · MySQL · PostgreSQL

1. Warum Datentyp-Entscheidungen schwer rückgängig zu machen sind

Eine Datentyp-Wahl wirkt beim Anlegen einer Tabelle oft wie eine Nebensächlichkeit, entfaltet ihre volle Tragweite aber erst, wenn eine Tabelle Millionen Zeilen erreicht hat. Ein ALTER TABLE, das den Typ einer Spalte ändert, sperrt in vielen Datenbanksystemen entweder die gesamte Tabelle oder erfordert das Umschreiben jeder einzelnen Zeile, was bei großen Tabellen Stunden dauern und produktive Systeme spürbar beeinträchtigen kann. Was bei der Tabellenerstellung eine Zeile Code ist, wird Monate später zu einer aufwendigen Migration mit Downtime-Fenster und Rollback-Plan.

Diese Asymmetrie zwischen der Leichtigkeit der ursprünglichen Entscheidung und dem Aufwand der späteren Korrektur macht die Datentyp-Wahl zu einer der Entscheidungen im Datenbankdesign mit dem höchsten Hebel. Ein zu klein gewählter Integer-Typ für einen Primärschlüssel, ein zu kurzes VARCHAR-Limit oder FLOAT statt DECIMAL für Geldbeträge sind typische Beispiele, bei denen die Folgen erst mit Skalierung sichtbar werden, dann aber schwerwiegend sind. Die folgenden Abschnitte behandeln die vier häufigsten Entscheidungsfelder im Detail: Primärschlüssel-Typen, Textlängen, Geldbeträge und Zeitangaben.

2. INT vs. BIGINT für Primärschlüssel

Ein INT in den meisten Datenbanksystemen ist vorzeichenbehaftet 32 Bit groß und deckt Werte bis etwa 2,1 Milliarden ab, unsigned bis etwa 4,3 Milliarden. Für viele Tabellen erscheint dieser Bereich zunächst komfortabel groß. Bei Tabellen mit hoher Schreibfrequenz, etwa Event-Logs, Analytics-Daten oder Bestellpositionen in einem stark frequentierten Shop, ist dieser Bereich jedoch schneller erschöpft, als viele Teams erwarten: Bei 5000 neuen Zeilen pro Sekunde ist die Grenze eines unsigned INT bereits nach etwa zehn Monaten erreicht.

Die Datentyp-Wahl BIGINT, 64 Bit groß mit einem praktisch unerschöpflichen Wertebereich, kostet pro Zeile lediglich vier zusätzliche Byte gegenüber INT, ein in den allermeisten Fällen vernachlässigbarer Mehraufwand. Da eine spätere Migration eines Primärschlüssels von INT auf BIGINT nicht nur die betroffene Tabelle, sondern auch jede referenzierende Fremdschlüssel-Spalte in allen verknüpften Tabellen betrifft, ist diese Migration eine der aufwendigsten überhaupt. Die pragmatische Regel: Bei jeder Tabelle, die potenziell schnell wachsen könnte, BIGINT von Anfang an verwenden, es sei denn, ein hartes technisches Limit spricht dagegen.


-- INT unsigned: fine for slow-growing lookup tables (max ~4.3 billion rows)
CREATE TABLE country (
    country_id  INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name        VARCHAR(100) NOT NULL
);

-- BIGINT unsigned: recommended for high-write-frequency tables
CREATE TABLE order_event (
    order_event_id  BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    order_id         BIGINT UNSIGNED NOT NULL,
    event_type       VARCHAR(50) NOT NULL,
    occurred_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- Estimate how fast an INT primary key would be exhausted
-- at a given insert rate (rows per second)
-- 4294967295 / rows_per_second / 86400 = days until exhaustion

3. VARCHAR-Längen richtig wählen

Bei VARCHAR-Spalten kollidieren zwei gegensätzliche Fehler: ein zu kurzes Limit, das legitime Werte abschneidet oder INSERT-Fehler produziert, und ein pauschales VARCHAR(255) für jede Textspalte unabhängig vom tatsächlichen Inhalt. Die Datentyp-Wahl bei VARCHAR sollte sich am realistischen Maximalwert der fachlichen Domäne orientieren: Ein Ländercode ist immer zwei oder drei Zeichen lang, eine E-Mail-Adresse hat laut RFC eine theoretische Obergrenze von 254 Zeichen, ein Produktname variiert stark und braucht eher großzügige 255 bis 500 Zeichen.

In modernen Datenbanksystemen wie PostgreSQL und MySQL mit InnoDB hat die VARCHAR-Länge selbst kaum Performance-Auswirkungen, weil nur die tatsächlich gespeicherte Zeichenzahl plus ein kurzer Längenpräfix Speicherplatz benötigt, nicht die deklarierte Maximallänge. Der eigentliche Wert einer durchdachten Längenbegrenzung liegt in der Dokumentation der fachlichen Domäne direkt im Schema und im Schutz vor fehlerhaften oder böswilligen übergroßen Eingaben, nicht in Speicherplatzersparnis.


CREATE TABLE customer (
    customer_id     BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    -- domain-driven lengths, not a blanket VARCHAR(255) everywhere
    email             VARCHAR(254) NOT NULL,        -- RFC 5321 limit
    country_code      CHAR(2) NOT NULL,               -- ISO 3166-1 alpha-2
    phone_number      VARCHAR(20) NULL,               -- E.164 max length
    company_name      VARCHAR(255) NULL,
    notes             TEXT NULL                       -- unbounded free text
);

4. Geld speichern: DECIMAL statt FLOAT

FLOAT und DOUBLE speichern Zahlen in binärer Gleitkommadarstellung, die viele dezimale Werte nicht exakt abbilden kann. Der Betrag 0,1 lässt sich in binärer Gleitkommadarstellung nicht exakt darstellen, wodurch Rechenoperationen wie 0,1 plus 0,2 in FLOAT-Arithmetik ein Ergebnis wie 0,30000000000000004 statt exakt 0,3 liefern können. Bei Geldbeträgen summieren sich solche Rundungsfehler über viele Transaktionen zu Differenzen, die in einer Buchhaltung oder einem Kassensystem nicht tolerierbar sind.

DECIMAL, in manchen Systemen NUMERIC genannt, speichert Zahlen als exakte dezimale Darstellung mit fest definierter Anzahl an Vorkomma- und Nachkommastellen. Die Datentyp-Wahl DECIMAL(10,2) für Geldbeträge in Euro erlaubt Beträge bis 99.999.999,99 mit exakt zwei Nachkommastellen, ohne jeden Rundungsfehler. Für Anwendungsfälle mit Fremdwährungen oder Kryptowährungen, die mehr Nachkommastellen benötigen, ist die Präzision entsprechend höher zu wählen, etwa DECIMAL(19,8) für Kryptowährungsbeträge.


-- WRONG: FLOAT introduces rounding errors in monetary calculations
CREATE TABLE bad_invoice (
    amount FLOAT   -- 0.1 + 0.2 may not equal exactly 0.3
);

-- RIGHT: DECIMAL stores an exact decimal representation
CREATE TABLE invoice (
    invoice_id   BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    amount        DECIMAL(10,2) NOT NULL,   -- up to 99,999,999.99
    currency      CHAR(3) NOT NULL,          -- ISO 4217
    tax_rate      DECIMAL(5,4) NOT NULL      -- up to 9.9999 (e.g. 0.1900)
);

-- Verify: with DECIMAL, 0.1 + 0.2 equals exactly 0.3
SELECT CAST(0.1 AS DECIMAL(10,2)) + CAST(0.2 AS DECIMAL(10,2));

5. Timestamps und Zeitzonen richtig behandeln

Ein häufiger Fehler bei der Datentyp-Wahl für Zeitangaben ist, Zeitstempel ohne Zeitzoneninformation zu speichern und implizit von der lokalen Serverzeitzone auszugehen. Sobald eine Anwendung international betrieben wird oder Server in unterschiedlichen Zeitzonen läuft, führt das zu inkonsistenten Zeitangaben, die sich nicht mehr zuverlässig vergleichen lassen. Der robuste Standardansatz ist, alle Zeitstempel intern konsequent in UTC zu speichern und die Umrechnung in die lokale Zeitzone des Nutzers erst in der Präsentationsschicht vorzunehmen.

PostgreSQL bietet mit TIMESTAMPTZ einen Spaltentyp, der Zeitzoneninformation bei der Eingabe berücksichtigt und intern konsistent in UTC speichert, was Fehler durch implizite Zeitzonenannahmen strukturell reduziert. MySQL kennt keinen echten TIMESTAMPTZ-Typ, TIMESTAMP wird dort automatisch von und nach der Session-Zeitzone konvertiert, während DATETIME zeitzonenunabhängig ist und die Umrechnung vollständig der Anwendung überlässt. Diese Unterschiede zwischen Datenbanksystemen machen eine bewusste, dokumentierte Konvention für den Umgang mit Zeitzonen unverzichtbar.


-- PostgreSQL: TIMESTAMPTZ normalizes input to UTC internally
CREATE TABLE event_log (
    event_log_id  BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    occurred_at     TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- MySQL: store UTC explicitly in a session-independent DATETIME column,
-- convert to the user's local time zone only in the presentation layer
CREATE TABLE event_log (
    event_log_id  BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    occurred_at     DATETIME NOT NULL DEFAULT (UTC_TIMESTAMP())
);

6. Boolean, ENUM und CHAR(1): typische Fallstricke

MySQL kennt historisch keinen echten BOOLEAN-Typ, BOOLEAN ist dort lediglich ein Alias für TINYINT(1), was in der Praxis dazu führen kann, dass eine vermeintliche Boolean-Spalte versehentlich Werte wie 2 oder minus 1 akzeptiert, wenn kein zusätzlicher CHECK-Constraint gesetzt ist. PostgreSQL bietet dagegen einen echten BOOLEAN-Typ mit strikter Prüfung. Diese Datentyp-Wahl sollte in MySQL immer durch einen expliziten CHECK-Constraint abgesichert werden, um dasselbe Sicherheitsniveau wie in PostgreSQL zu erreichen.

ENUM-Spaltentypen wirken auf den ersten Blick praktisch, weil sie ungültige Werte direkt auf Spaltenebene verhindern, sind aber bei häufigen Wertänderungen unflexibel: In MySQL erfordert das Hinzufügen eines neuen ENUM-Werts ein ALTER TABLE, das je nach Position des neuen Werts sogar die gesamte Tabelle umschreiben kann. Ein VARCHAR mit begleitendem CHECK-Constraint oder einer separaten Lookup-Tabelle ist in den meisten Fällen die flexiblere und langfristig wartbarere Alternative zu ENUM. CHAR(1) für Flags wie 'Y'/'N' oder 'J'/'N' sollte generell vermieden werden zugunsten eines echten BOOLEAN oder eines dokumentierten ENUM mit aussagekräftigen Werten.


-- MySQL: BOOLEAN is just TINYINT(1), enforce it explicitly
CREATE TABLE product (
    product_id  BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    is_active     TINYINT(1) NOT NULL DEFAULT 1,
    CONSTRAINT chk_is_active_bool CHECK (is_active IN (0, 1))
);

-- More maintainable than ENUM for values that change often:
-- VARCHAR with CHECK instead of a hardcoded value list in the schema
CREATE TABLE product (
    product_id  BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    status        VARCHAR(20) NOT NULL,
    CONSTRAINT chk_status_values
        CHECK (status IN ('draft', 'active', 'discontinued'))
);
Anwendungsfall Falsche Wahl Empfohlener Datentyp Grund
Primärschlüssel, hohe Schreibrate INT BIGINT UNSIGNED Wertebereich erschöpft sich schneller als erwartet
Geldbetrag FLOAT / DOUBLE DECIMAL(10,2) Exakte dezimale Darstellung ohne Rundungsfehler
Zeitstempel, internationale Nutzung Lokale Zeit ohne Zone UTC (TIMESTAMPTZ) Konsistente Vergleichbarkeit über Zeitzonen hinweg
Ja/Nein-Flag CHAR(1) 'Y'/'N' BOOLEAN mit CHECK Typsicherheit statt freier Textwerte
Häufig wechselnde Statuswerte ENUM VARCHAR + CHECK / Lookup-Tabelle Neue Werte ohne teures ALTER TABLE

7. TEXT vs. VARCHAR: wann was

VARCHAR mit fester Maximallänge eignet sich für Werte mit einer natürlichen, fachlich begründbaren Obergrenze: Namen, E-Mail-Adressen, Postleitzahlen, Statuscodes. TEXT ohne feste Längenbegrenzung eignet sich für Inhalte, deren Länge fachlich nicht vorhersehbar ist, etwa Freitextkommentare, Artikelinhalte oder Log-Nachrichten. Die Datentyp-Wahl zwischen beiden ist keine reine Performance-Frage, sondern eine Frage der korrekten Domänenmodellierung: Ein VARCHAR-Limit auf einer Kommentarspalte behauptet implizit eine fachliche Obergrenze, die oft gar nicht existiert.

In PostgreSQL gibt es intern kaum einen Performance-Unterschied zwischen VARCHAR und TEXT, weil beide denselben zugrunde liegenden Speichermechanismus nutzen. In MySQL mit InnoDB kann eine sehr lange VARCHAR-Deklaration dagegen die maximale Zeilengröße oder die Anzahl möglicher Indizes auf der Tabelle beeinflussen, weshalb TEXT für wirklich unbegrenzte Inhalte dort die technisch sauberere Wahl ist. Ein Index auf eine TEXT-Spalte erfordert in den meisten Systemen ein explizites Präfix-Limit, etwa die ersten 100 Zeichen, was bei der Planung von Volltextsuchen berücksichtigt werden muss.

8. Datentyp-Migration im laufenden Betrieb

Eine spätere Korrektur der Datentyp-Wahl ist bei kleinen Tabellen unkompliziert, bei großen Tabellen im produktiven Betrieb aber ein eigenständiges Projekt. Der sichere Weg für einen Typwechsel bei einer stark frequentierten Tabelle führt über eine neue Spalte statt über ein direktes ALTER TABLE MODIFY: Zuerst wird die neue Spalte mit dem Zieltyp hinzugefügt, dann werden bestehende Werte schrittweise in Batches kopiert und konvertiert, parallel schreibt die Anwendung bereits in beide Spalten, erst nach vollständiger Validierung wird die alte Spalte entfernt und die neue umbenannt.

Für den klassischen Fall INT zu BIGINT bei einem Primärschlüssel kommt erschwerend hinzu, dass jede referenzierende Fremdschlüssel-Spalte in allen verknüpften Tabellen dieselbe Migration synchron durchlaufen muss, da eine Typinkonsistenz zwischen Primär- und Fremdschlüssel in den meisten Datenbanksystemen entweder abgelehnt wird oder zu impliziten, teuren Typkonvertierungen bei jedem JOIN führt. Werkzeuge wie pt-online-schema-change für MySQL oder pg_repack für PostgreSQL automatisieren diesen mehrstufigen Prozess und minimieren dabei Sperrzeiten auf ein Minimum.

9. Checkliste für neue Spalten

Vor jeder neuen Spalte lohnt sich eine kurze, aber systematische Prüfung der Datentyp-Wahl: Wächst diese Tabelle potenziell auf mehrere Millionen Zeilen, sodass BIGINT statt INT für Referenzspalten sinnvoll ist? Hat der Textwert eine fachlich begründbare Obergrenze, oder ist TEXT die ehrlichere Wahl? Handelt es sich um einen Geldbetrag, der zwingend DECIMAL statt FLOAT braucht? Wird die Spalte international genutzt und braucht deshalb eine explizite UTC-Konvention statt impliziter lokaler Zeit?

Diese vier Fragen decken die häufigsten kostspieligen Fehler bei der Datentyp-Wahl ab und lassen sich in wenigen Minuten beim Design einer neuen Tabelle beantworten, während eine spätere Korrektur Tage bis Wochen an Migrationsaufwand bedeuten kann. Ein kurzer Code-Review-Check speziell für neue CREATE TABLE und ALTER TABLE ADD COLUMN Statements, der genau diese vier Punkte prüft, verhindert die teuersten Überraschungen zuverlässig.

Mironsoft

Datenmodellierung, Schema-Design und Datenbankberatung

Ein Schema, das dem Wachstum eures Systems standhält?

Wir prüfen bestehende Spaltentypen auf typische Risikomuster, planen sichere Migrationen für kritische Tabellen und begleiten die Umstellung ohne Downtime für euren produktiven Betrieb.

Typ-Audit

Systematische Prüfung auf INT-Erschöpfung, FLOAT bei Geld und fehlende Zeitzonen

Sichere Migration

Batch-basierte Typwechsel ohne Sperrzeiten auf großen Tabellen

Schema-Review

Präventive Checkliste für neue Tabellen und Spalten im Code-Review

10. Zusammenfassung

Die Datentyp-Wahl gehört zu den Entscheidungen im Datenbankdesign mit der größten Diskrepanz zwischen anfänglichem Aufwand und späteren Konsequenzen. BIGINT statt INT für wachsende Primärschlüssel, realistische statt pauschale VARCHAR-Längen, DECIMAL statt FLOAT für jeden Geldbetrag und konsequente UTC-Speicherung für Zeitstempel sind vier Regeln, die sich beim Anlegen einer Tabelle in Sekunden umsetzen lassen, deren nachträgliche Korrektur aber Tage bis Wochen an Migrationsaufwand kosten kann.

Boolean-Flags mit echtem Typ statt CHAR(1), ENUM nur bei wirklich stabilen Wertelisten und die bewusste Unterscheidung zwischen VARCHAR mit fachlicher Obergrenze und TEXT für unbegrenzte Inhalte runden eine solide Datentyp-Wahl ab. Wer diese Punkte systematisch in jedem Schema-Review prüft, vermeidet die teuersten und am schwersten rückgängig zu machenden Fehler im Datenbankdesign.

Datentyp-Wahl mit langfristigen Folgen, das Wichtigste auf einen Blick

Primärschlüssel

BIGINT UNSIGNED statt INT bei jeder potenziell schnell wachsenden Tabelle.

Geldbeträge

Immer DECIMAL mit fester Präzision, niemals FLOAT oder DOUBLE.

Zeitstempel

Konsequent in UTC speichern, lokale Umrechnung erst in der Präsentationsschicht.

Text und Flags

VARCHAR mit fachlicher Obergrenze, TEXT für Unbegrenztes, echter BOOLEAN statt CHAR(1).

11. FAQ: Datentyp-Wahl mit langfristigen Folgen

1Wann BIGINT statt INT für einen Primärschlüssel?
Immer bei potenziell schnell wachsenden Tabellen. BIGINT kostet nur vier zusätzliche Byte, spätere Migration ist dagegen sehr aufwendig.
2Warum keine Geldbeträge als FLOAT speichern?
FLOAT kann viele dezimale Werte nicht exakt abbilden. Rundungsfehler summieren sich über Transaktionen zu inakzeptablen Differenzen.
3Welche Präzision für DECIMAL bei Euro?
DECIMAL(10,2) deckt Beträge bis 99.999.999,99 mit zwei Nachkommastellen ab, für die meisten Anwendungen ausreichend.
4Wie speichere ich Zeitstempel international korrekt?
Konsequent in UTC, Umrechnung in lokale Zeit erst in der Präsentationsschicht, nicht in der Datenbank selbst.
5Ist BOOLEAN in MySQL ein echter Typ?
Nein, nur ein Alias für TINYINT(1). Ein expliziter CHECK-Constraint verhindert ungültige Werte außerhalb von 0 und 1.
6Wann VARCHAR statt TEXT verwenden?
Bei einer fachlich begründbaren Obergrenze. Für unvorhersehbar lange Inhalte ist TEXT die ehrlichere Modellierung.
7Warum ist ENUM bei häufigen Änderungen problematisch?
Neue Werte erfordern in MySQL ein ALTER TABLE, das die Tabelle umschreiben kann. VARCHAR mit CHECK oder eine Lookup-Tabelle ist flexibler.
8Wie migriere ich INT zu BIGINT ohne Downtime?
Über neue Spalte, Batch-Kopie, parallele Schreibvorgänge und Entfernen der alten Spalte nach Validierung, automatisiert mit pt-online-schema-change oder pg_repack.
9Beeinflusst VARCHAR-Länge die Performance?
In PostgreSQL kaum, nur die tatsächliche Zeichenzahl benötigt Speicher. Der Wert der Längenbegrenzung liegt vor allem in der Domänendokumentation.
10Was gehört auf eine Checkliste für neue Spalten?
Wachstumspotenzial für BIGINT, fachliche Textobergrenze, DECIMAL bei Geld und UTC-Konvention bei internationaler Nutzung prüfen.