Timestamps in Datenbanken speichern: DATETIME vs. INT vs. BIGINT

Den falschen Spaltentyp für Timestamps zu wählen führt zu Zeitzonen-Drift, dem Jahr-2038-Überlauf, kaputten Bereichsabfragen und verwirrender API-Ausgabe. Vergleiche native datetime-Typen, BIGINT-Epoch-Werte und Strings in MySQL und PostgreSQL.

Drei Wege, einen Timestamp zu speichern

Die meisten Datenbanken bieten mindestens drei Optionen: einen nativen datetime-Typ (TIMESTAMP, DATETIME, TIMESTAMPTZ), eine einfache Ganzzahl (INT oder BIGINT) oder einen String (VARCHAR). Jede hat andere Kompromisse bei Speichergröße, Abfrage-Ergonomie, Zeitzonen-Handhabung und Zukunftssicherheit. Für die meisten Produktdatenbanken ist eine native datetime-Spalte der beste Standard, weil die Datenbank den Wert als Zeit statt als anonyme Zahl vergleichen, indizieren, kürzen, gruppieren und formatieren kann.

  • Nativer datetime-Typ — am besten für Datumsarithmetik, Zeitzonen-Umwandlung und Lesbarkeit
  • BIGINT-Ganzzahl — gut für Inserts mit hohem Durchsatz und einfache numerische Bereichsabfragen
  • VARCHAR-String — fast immer falsch: String-Vergleich von Daten funktioniert nur mit striktem ISO-8601-Format
  • INT-Ganzzahl — für zukünftige Timestamps vermeiden, außer du hast die Jahr-2038-Grenze vollständig geprüft

MySQL: TIMESTAMP vs. DATETIME vs. INT

MySQL hat zwei Datum-Zeit-Typen, die ähnlich aussehen, sich aber sehr unterschiedlich verhalten — und einer hat ein hartes Ablaufdatum. TIMESTAMP ist praktisch, wenn du automatische Umwandlung zwischen UTC und Session-Zeitzone willst, aber sein historischer 32-Bit-Bereich macht ihn riskant für zukunftsgerichtete Produktdaten. DATETIME speichert das gelieferte literale Datum und die Uhrzeit, was meist klarer ist, wenn die Anwendung vor dem Schreiben auf UTC standardisiert.

  • TIMESTAMP: intern als 32-Bit-Unix-Sekunden gespeichert — begrenzt von 1970-01-01 bis 2038-01-19
  • TIMESTAMP: wandelt beim Einfügen/Lesen automatisch zwischen UTC und Session-Zeitzone um
  • DATETIME: speichert die literale Datum-Zeit, keine Zeitzone. Bereich 1000-01-01 bis 9999-12-31. Nicht vom Y2038 betroffen.
  • DATETIME: wandelt keine Zeitzonen um — du steuerst UTC auf Anwendungsebene
  • Empfehlung: nutze DATETIME mit expliziten UTC-Werten für neue Tabellen, um die 2038-Grenze zu vermeiden

PostgreSQL: TIMESTAMPTZ ist die richtige Wahl

PostgreSQLs TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ) speichert Timestamps intern als UTC-Mikrosekunden und wandelt bei der Ausgabe in die Session-Zeitzone um. Es ist für die meisten Fälle die sicherste und korrekteste Option, weil es einen echten Moment darstellt. Der Name kann irreführen: TIMESTAMPTZ speichert nicht das ursprüngliche Zeitzonen-Label wie America/New_York. Es speichert den Moment und zeigt ihn dann gemäß der aktuellen Session-Zeitzone.

  • TIMESTAMPTZ: speichert UTC, wandelt bei der Ausgabe in die Session-Zeitzone — portabel und sommerzeitsicher
  • TIMESTAMP (ohne Zeitzone): speichert den literalen Wert ohne Umwandlung — nur für zeitzonen-naive Daten nutzen
  • EXTRACT(EPOCH FROM col): gibt Unix-Sekunden als Float aus jeder TIMESTAMP-Spalte zurück
  • TO_TIMESTAMP(epoch): wandelt Unix-Sekunden zurück in ein TIMESTAMPTZ

Indizierung und Abfrageleistung

Für normale Anwendungstabellen ist der Leistungsunterschied zwischen nativen datetime-Spalten und BIGINT-Epoch-Spalten selten der entscheidende Faktor. Abfrageform, Index-Design, Partitionierung und Zeilenzahl zählen mehr. Wähle zuerst den Typ, der die Bedeutung korrekt hält, dann indiziere ihn für die Bereichsabfragen, die deine Anwendung wirklich ausführt.

  • Alle drei Typen unterstützen B-Tree-Indizes und effiziente Bereichsabfragen
  • BIGINT-Ganzzahlen sind bei Gleichheits- und Bereichsscans auf Tabellen mit sehr hohem Volumen marginal schneller
  • Native datetime-Typen erlauben indizierte Datumsteil-Abfragen: WHERE created_at::date = '2024-01-01'
  • VARCHAR-Timestamps sind am schlechtesten für die Leistung — String-Vergleich versteht keine Daten

Wann BIGINT-Epoch-Speicherung sinnvoll ist

BIGINT ist sinnvoll, wenn die Daten ereignisartig, einfügelastig und bereits von einem anderen System als Unix-Zeit erzeugt sind. Analytics-Pipelines, Telemetrie-Streams, Queues und kompakte Binärprotokolle nutzen oft Epoch-Millisekunden, weil numerische Werte schnell zu vergleichen und sprachneutral sind. Der Kompromiss ist Lesbarkeit: Menschen brauchen einen Konverter und SQL-Datumsarithmetik wird ausführlicher.

  • Nutze BIGINT für Unix-Millisekunden, wenn JavaScript-Clients die Ereignisse direkt erzeugen
  • Nutze BIGINT für Unix-Sekunden, wenn das Quellsystem Unix-artig ist und Sekundengenauigkeit reicht
  • Dokumentiere die Einheit im Spaltennamen: created_at_ms ist klarer als created_at_epoch
  • Füge eine generierte datetime-Spalte hinzu, wenn Analysten lesbare SQL-Abfragen brauchen
  • Vermeide INT für moderne zukunftsgerichtete Timestamps wegen der 32-Bit-Bereichsgrenzen

Empfohlene Schema-Muster

Für die meisten Webanwendungen speichere einen Moment in UTC und speichere die bevorzugte Zeitzone des Nutzers nur dann separat, wenn du die lokale Wanduhr-Absicht rekonstruieren musst. Ein für 9:00 Uhr America/New_York geplantes Meeting unterscheidet sich von einem zu einem präzisen UTC-Moment erstellten Ereignis-Log; modelliere diese Fälle unterschiedlich.

  • Ereignis-Logs: created_at TIMESTAMPTZ in PostgreSQL oder created_at DATETIME in UTC für MySQL
  • JavaScript-Ereignis-Ingestion: created_at_ms BIGINT plus klare API-Dokumentation
  • Wiederkehrende lokale Pläne: local_date, local_time und timezone_id, dann den nächsten Moment berechnen
  • Ablauf-Timestamps: expires_at als nativer datetime oder expires_at_seconds mit expliziten Unix-Sekunden
  • Audit-Tabellen: behalte created_at und updated_at als native datetime-Spalten für lesbares Debugging

FAQ zu Datenbank-Timestamps

Soll ich UTC oder lokale Zeit in einer Datenbank speichern?
Speichere UTC für Ereignis-Timestamps und wandle bei der Anzeige in lokale Zeit um. Speichere einen Zeitzonen-Identifier separat, wenn die lokale Wanduhr-Absicht des Nutzers wichtig ist, etwa wiederkehrende Meetings oder Geschäftszeiten.
Ist BIGINT besser als TIMESTAMP?
Nicht generell. BIGINT ist nützlich für numerische Epoch-Pipelines, aber native datetime-Typen sind einfacher für SQL-Datumsarithmetik, lesbares Debugging und zeitzonenbewusste Ausgabe.
Soll MySQL TIMESTAMP oder DATETIME nutzen?
Für neue Anwendungstabellen ist DATETIME mit UTC-Werten oft sicherer, weil es die 2038-Bereichsgrenze vermeidet und nicht still von der Session-Zeitzonen-Umwandlung abhängt.
Soll ich Timestamps in UTC oder mit einer Zeitzone speichern?
Speichere den Moment in UTC (TIMESTAMPTZ in PostgreSQL oder DATETIME mit UTC-Werten in MySQL) und wandle bei der Anzeige in lokale Zeit um. Behalte eine separate IANA-Zeitzonen-Spalte nur, wenn du die lokale Wanduhr-Absicht des Nutzers rekonstruieren musst, etwa wiederkehrende Meetings.