automatische Historie nach dem SQL:2011-Standard
Wer den Zustand einer Zeile zu einem beliebigen Zeitpunkt in der Vergangenheit rekonstruieren muss, baut klassischerweise eine eigene Audit-Trail-Tabelle mit Triggern oder Anwendungscode. Der SQL:2011-Standard definiert dafür eine robustere Alternative: system-versionierte Tabellen, bei denen die Datenbank selbst bei jedem UPDATE und DELETE automatisch eine vollständige Historie mit gültigen Zeiträumen pflegt, ganz ohne zusätzlichen Anwendungscode. SQL Server und MariaDB implementieren dieses Konzept bereits produktionsreif, mit eigener Syntax für Zeitreise-Abfragen. Dieser Artikel zeigt, wie die Technik konkret funktioniert, wo sie sich von handgebauten Audit-Trails unterscheidet und wo ihre Grenzen liegen.
Inhaltsverzeichnis
- 1. Wie System-Versioning automatisch eine History-Tabelle pflegt
- 2. Der Unterschied zu manuell gebauten Audit-Trail-Tabellen
- 3. System-Versioned Temporal Tables in SQL Server im Detail
- 4. System-Versioned Tables in MariaDB
- 5. Historische Daten mit FOR SYSTEM_TIME abfragen
- 6. Verfügbarkeitslücken: welche Datenbanken system-versionierte Tabellen unterstützen
- 7. Speicherwachstum und Performance-Auswirkungen der History-Tabelle
- 8. Was System-Versioning bewusst nicht abdeckt
- 9. Praxisempfehlung: wann sich System-Versioning lohnt
- 10. Zusammenfassung
- 11. FAQ
1. Wie System-Versioning automatisch eine History-Tabelle pflegt
Bei einer system-versionierten Tabelle erhält jede Zeile zwei zusätzliche, von der Datenbank verwaltete Zeitstempel-Spalten: einen Beginn- und einen Ende-Zeitpunkt der Gültigkeit aus Sicht des Systems. Führt eine Anwendung ein UPDATE oder DELETE aus, schreibt die Engine automatisch und innerhalb derselben Transaktion die vorherige Version der Zeile mit geschlossenem Gültigkeitszeitraum in eine Historientabelle, bevor die aktuelle Tabelle die neue Version erhält. Für die Anwendung ändert sich am eigentlichen SQL-Statement dabei nichts.
Der entscheidende Unterschied zu klassischen Zeitstempel-Spalten wie updated_at liegt in der Vollständigkeit: Es gibt keinen Codepfad, über den eine Änderung an der History vorbeigeschrieben werden kann, weil die Logik direkt im Storage Layer der Datenbank verankert ist. Selbst ein Bulk-Update über tausend Zeilen erzeugt zuverlässig tausend History-Einträge, ohne dass ein einziger Trigger dafür manuell gepflegt werden müsste.
-- SQL Server: system-versionierte Tabelle anlegen
CREATE TABLE dbo.vertrag (
vertrag_id INT PRIMARY KEY,
kunde_id INT NOT NULL,
status VARCHAR(20) NOT NULL,
gueltig_von DATETIME2 GENERATED ALWAYS AS ROW START,
gueltig_bis DATETIME2 GENERATED ALWAYS AS ROW END,
PERIOD FOR SYSTEM_TIME (gueltig_von, gueltig_bis)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.vertrag_history));
2. Der Unterschied zu manuell gebauten Audit-Trail-Tabellen
Ein klassischer Audit-Trail entsteht meist über einen Trigger, der bei UPDATE und DELETE die alte Zeile in eine separate Log-Tabelle kopiert, oder über Anwendungscode, der diese Aufgabe im ORM übernimmt. Beide Varianten funktionieren, solange jeder Schreibpfad diszipliniert durch dieselbe Schicht läuft. Sobald jemand ein direktes SQL-Statement gegen die Datenbank absetzt, einen Migrationslauf ohne ORM-Hooks ausführt oder ein Trigger versehentlich deaktiviert wird, entstehen stille Lücken in der Historie, die oft erst auffallen, wenn sie bereits gebraucht würde.
System-Versioning eliminiert genau diese Fehlerklasse, weil die Historienpflege nicht an Anwendungslogik gebunden ist, sondern an das Schreiben der Zeile selbst. Der Preis dafür ist weniger Flexibilität: Wer zusätzliche Metadaten wie den Verursacher einer Änderung mitschreiben will, muss dafür weiterhin eigene Spalten und eigene Logik ergänzen, denn das reine System-Versioning kennt nur den Zeitpunkt der Änderung, nicht deren Urheber.
3. System-Versioned Temporal Tables in SQL Server im Detail
SQL Server unterstützt System-Versioned Temporal Tables seit Version 2016. Die beiden generierten Spalten für Beginn und Ende der Gültigkeit werden über PERIOD FOR SYSTEM_TIME deklariert, und die Option SYSTEM_VERSIONING = ON verknüpft die Basistabelle mit einer eigenständigen Historientabelle. Diese Historientabelle lässt sich explizit benennen, wie im Beispiel oben, oder von SQL Server automatisch generieren lassen, inklusive eines passenden Clustered-Columnstore-Index für effiziente Komprimierung großer Historienbestände.
Über die Option HISTORY_RETENTION_PERIOD lässt sich außerdem festlegen, wie lange Historieneinträge aufbewahrt werden, bevor SQL Server sie automatisch über einen Hintergrundjob löscht. Das ist besonders bei Tabellen mit hoher Änderungsfrequenz relevant, denn ohne eine solche Grenze wächst die Historientabelle unbegrenzt und kann die eigentliche Nutztabelle nach wenigen Monaten um ein Vielfaches übersteigen.
4. System-Versioned Tables in MariaDB
MariaDB implementiert System-Versioning seit Version 10.3 mit der Klausel WITH SYSTEM VERSIONING direkt am CREATE TABLE-Statement. Anders als SQL Server legt MariaDB standardmäßig keine separate Historientabelle an, sondern hält alte und aktuelle Zeilen in derselben Tabelle, unterscheidbar über die intern verwalteten Systemspalten row_start und row_end. Abfragen ohne explizite Zeitangabe sehen automatisch nur die aktuell gültigen Zeilen, als wäre die Historie gar nicht vorhanden.
Für die Verwaltung des Speicherwachstums bietet MariaDB System-Versioning Partitions an, mit denen sich historische Zeilen nach Alter in separate Partitionen verschieben und über PARTITION ... HISTORY automatisiert wieder verwerfen lassen. Das entspricht funktional der Retention-Period von SQL Server, nur über den etablierten Partitionierungsmechanismus statt über eine dedizierte Konfigurationsoption.
-- MariaDB: system-versionierte Tabelle anlegen
CREATE TABLE vertrag (
vertrag_id INT PRIMARY KEY,
kunde_id INT NOT NULL,
status VARCHAR(20) NOT NULL
) WITH SYSTEM VERSIONING;
-- Aktuelle Zeile ändern, MariaDB schreibt die alte Version automatisch fort
UPDATE vertrag SET status = 'gekuendigt' WHERE vertrag_id = 42;
5. Historische Daten mit FOR SYSTEM_TIME abfragen
Beide Systeme verwenden dieselbe standardisierte Syntax für Zeitreise-Abfragen: FOR SYSTEM_TIME AS OF liefert den Zustand zu einem exakten Zeitpunkt, FOR SYSTEM_TIME BETWEEN und FOR SYSTEM_TIME FROM TO liefern alle Versionen innerhalb eines Intervalls mit leicht unterschiedlicher Grenzbehandlung, und FOR SYSTEM_TIME ALL liefert die komplette Historie einer Zeile inklusive aller Zwischenversionen.
Für die Praxis bedeutet das: Statt eine eigene Rekonstruktionslogik über mehrere JOINs gegen eine Historientabelle zu schreiben, genügt eine einzige Zeitangabe im FROM-Teil der Abfrage, und die Datenbank übernimmt die komplette Auswahl der jeweils passenden Version automatisch, auch über Tabellengrenzen hinweg in JOINs mit weiteren system-versionierten Tabellen.
-- Zustand des Vertrags am Stichtag 2026-03-01, 12:00 Uhr rekonstruieren
SELECT vertrag_id, status
FROM vertrag
FOR SYSTEM_TIME AS OF '2026-03-01 12:00:00'
WHERE vertrag_id = 42;
-- Alle Statusänderungen eines Vertrags im ersten Quartal auflisten
SELECT vertrag_id, status, gueltig_von, gueltig_bis
FROM vertrag
FOR SYSTEM_TIME BETWEEN '2026-01-01' AND '2026-03-31'
WHERE vertrag_id = 42
ORDER BY gueltig_von;
6. Verfügbarkeitslücken: welche Datenbanken system-versionierte Tabellen unterstützen
PostgreSQL besitzt bis heute keine native Implementierung von System-Versioning nach SQL:2011. Wer diese Funktionalität dort braucht, greift entweder auf die Extension temporal_tables zurück, die über Trigger eine ähnliche History-Tabelle nachbildet, oder baut die Lösung von Hand mit Range-Typen und Exclusion Constraints nach. Auch reines MySQL, ohne den MariaDB-Fork, bietet keine eingebaute Unterstützung.
IBM DB2 gehörte historisch zu den ersten Systemen mit vollständiger SQL:2011-Unterstützung und deckt sowohl System-Period- als auch Application-Period-Tabellen ab. Oracle bietet mit Flashback Data Archive ein funktional ähnliches, aber syntaktisch eigenständiges Konzept, das ebenfalls automatisch Historie pflegt, jedoch nicht über die genormte FOR SYSTEM_TIME-Syntax abgefragt wird, sondern über eigene AS OF-Klauseln.
7. Speicherwachstum und Performance-Auswirkungen der History-Tabelle
Jede Änderung an einer system-versionierten Zeile erzeugt einen zusätzlichen Schreibvorgang in die Historie, zusätzlich zum eigentlichen UPDATE. Bei Tabellen mit sehr hoher Änderungsfrequenz, etwa Statuswechseln in Warteschlangen oder häufig aktualisierten Zählern, kann sich dieser doppelte Schreibaufwand spürbar auf die Latenz einzelner Transaktionen auswirken und sollte vor dem produktiven Einsatz mit realistischer Last gemessen werden.
Für lesende Zugriffe auf die Historie lohnt sich ein eigener Index auf den Zeitraumsspalten, da sonst jede Zeitreise-Abfrage einen vollständigen Tabellenscan der Historientabelle auslöst. In Kombination mit einer klaren Retention-Strategie, sei es über HISTORY_RETENTION_PERIOD in SQL Server oder History-Partitionen in MariaDB, bleibt die Historientabelle auch bei Millionen von Änderungen pro Jahr beherrschbar.
8. Was System-Versioning bewusst nicht abdeckt
System-Versioning kennt nur eine einzige Zeitachse, nämlich wann eine Zeile aus Sicht der Datenbank gültig war. Wann ein Sachverhalt fachlich, also aus Geschäftssicht, gültig war oder sein wird, bildet der Mechanismus nicht ab. Für eine rückwirkend korrigierte Rechnung etwa unterscheidet System-Versioning nicht zwischen dem Zeitpunkt der Korrektur im System und dem Zeitpunkt, ab dem die korrigierte Angabe fachlich gelten sollte. Wer diese Unterscheidung braucht, benötigt zusätzlich eine bitemporale Modellierung.
Auch Schema-Änderungen an der Basistabelle verdienen besondere Aufmerksamkeit: Eine neue Spalte muss in SQL Server in der Regel auch in der Historientabelle nachgezogen werden, und während die Versionierung aktiv ist, sind bestimmte DDL-Operationen eingeschränkt oder erfordern ein vorübergehendes Abschalten von SYSTEM_VERSIONING, was die History-Kette an dieser Stelle unterbricht und dokumentiert werden sollte.
9. Praxisempfehlung: wann sich System-Versioning lohnt
System-Versioning zahlt sich vor allem dort aus, wo eine lückenlose Nachvollziehbarkeit gesetzlich gefordert ist, etwa bei Vertrags-, Preis- oder Statusänderungen in regulierten Branchen, oder wo Entwickler regelmäßig rekonstruieren müssen, wie ein bestimmter Datensatz vor einem fehlerhaften Deployment aussah. Die Technik ersetzt in diesen Fällen eine ganze Kategorie von selbst geschriebenem, fehleranfälligem Trigger-Code durch eine geprüfte Datenbankfunktion.
Weniger sinnvoll ist der Einsatz bei Tabellen mit extrem hoher Schreiblast ohne echten Auditbedarf, da der zusätzliche Schreibaufwand dort reinen Overhead darstellt, sowie überall dort, wo tatsächlich Bitemporalität statt reiner Systemhistorie gebraucht wird. Für solche Anforderungen ist eine bewusst bitemporale Modellierung mit getrennten Zeitachsen für Gültigkeit und Erfassung die passendere Lösung.
| Merkmal | SQL Server | MariaDB | Manueller Audit-Trail |
|---|---|---|---|
| Historientabelle | Separates, benanntes Objekt | Meist dieselbe Tabelle mit Sichtbarkeitsfenster | Muss selbst modelliert werden |
| Konsistenz bei Bulk-Updates | Garantiert durch die Engine | Garantiert durch die Engine | Abhängig von Trigger-Abdeckung |
| Zeitreise-Abfrage | FOR SYSTEM_TIME |
FOR SYSTEM_TIME |
Eigene JOIN-Logik nötig |
| Erfassung des Verursachers | Nicht enthalten | Nicht enthalten | Frei modellierbar |
| Verfügbar seit | SQL Server 2016 | MariaDB 10.3 | Datenbankunabhängig, jederzeit |
Mironsoft
Datenbank-Optimierung, Query-Tuning und Migrationen
SQL-Abfragen, die bei Wachstum immer langsamer werden?
Wir analysieren und optimieren SQL-Datenbanken unabhängig vom eingesetzten System, planen sichere Migrationen und Schema-Änderungen und bringen Teams Query-Optimierung praxisnah bei.
Query-Optimierung
Langsame Abfragen analysieren und mit Indizes und Explain-Plänen gezielt beschleunigen.
Migrations-Planung
Schema-Änderungen und Datenmigrationen sicher und ohne Downtime umsetzen.
Team-Schulung
SQL-Grundlagen und Performance-Denken praxisnah im Entwicklerteam verankern.
10. Zusammenfassung
System-versionierte Tabellen: das Wichtigste auf einen Blick
Automatik
Die Datenbank pflegt History-Zeilen selbst, ohne Trigger oder eigenen Anwendungscode.
SQL Server
PERIOD FOR SYSTEM_TIME plus separate Historientabelle, produktionsreif seit Version 2016.
MariaDB
WITH SYSTEM VERSIONING seit Version 10.3, Historie meist direkt in derselben Tabelle.
Grenze
Nur eine Zeitachse: für echte Bitemporalität ist ein zusätzliches Modell nötig.