Soft delete, Audit-Logs, DSGVO: Was wirklich passiert, wenn du Daten „löschst", und warum Vergessen eine Architekturentscheidung ist.

5 min Lesezeit|Von Kaya
Datenbank-Architektur mit Soft Delete und Audit-Logs im Kontext der DSGVO
§ Inhalt · 5 Abschnitte

Eine Datenbank vergisst nichts. Aber sie versteht auch nichts.

Das ist kein Fehler. Das ist ihr Wesen. Sie speichert, was du ihr gibst. Sie löscht, was du ihr befiehlst. Und sie schweigt, wenn du die falsche Frage stellst. Die Verantwortung, was vergessen werden darf und was nicht, liegt immer bei dir, nicht beim System.

Ich habe in vielen Projekten gesehen, wie dieser Unterschied übersehen wird. Ein Knopf heißt "Löschen". Der Nutzer klickt. Die Zeile verschwindet aus der Oberfläche. Und irgendwo in einer PostgreSQL-Tabelle lebt der Datensatz weiter. Oder er ist wirklich weg, für immer, ohne Protokoll, ohne Spur. Beides kann falsch sein.

Löschen ist keine binäre Entscheidung

Es gibt zwei grundlegende Strategien: Hard Delete und Soft Delete. Beide klingen technisch. Aber hinter ihnen stecken Fragen, die weit über Code hinausgehen.

Ein Hard Delete ist endgültig. DELETE FROM users WHERE id = $1, und die Zeile ist weg. Kein Recovery, kein Rückblick, kein "Wann wurde das eigentlich gelöscht?" Für bestimmte Daten ist das genau richtig. Die DSGVO verlangt in manchen Fällen explizit das vollständige Entfernen personenbezogener Daten. Das Recht auf Vergessenwerden ist kein Feature. Es ist eine gesetzliche Pflicht.

Ein Soft Delete hingegen markiert. Die Zeile bleibt. Sie bekommt ein Timestamp-Feld, einen Boolean, eine Spalte deleted_at. Sie ist "logisch gelöscht", aber physisch vorhanden.

-- Soft Delete: Spalte hinzufügen
ALTER TABLE users ADD COLUMN deleted_at TIMESTAMPTZ DEFAULT NULL;

-- Löschen bedeutet jetzt: markieren
UPDATE users
SET deleted_at = NOW()
WHERE id = $1;

-- Alle aktiven Nutzer
SELECT * FROM users
WHERE deleted_at IS NULL;

-- Alle gelöschten Nutzer (für Audit, Admin, Recovery)
SELECT * FROM users
WHERE deleted_at IS NOT NULL;

Das klingt elegant. Und es ist elegant, solange du weißt, was du damit tust. Denn ein Soft Delete ist keine Datenschutzlösung. Es ist eine Architekturentscheidung. Wer Zugriff auf die Datenbank hat, sieht alles. Wer die falsche Query schreibt, zeigt dem Nutzer seine eigenen "gelöschten" Daten wieder an.

Das Audit-Log: Gedächtnis mit Absicht

Es gibt Dinge, die du nicht vergessen darfst. Nicht weil du es willst, sondern weil Systeme ohne Gedächtnis blind werden.

Wer hat was wann geändert? Welcher Admin hat einen Datensatz bearbeitet? Welche Preise galten zum Zeitpunkt einer Bestellung? Diese Fragen kommen immer dann, wenn es wehtut: bei einem Rechtsstreit, bei einem Supportfall, bei einem Sicherheitsvorfall.

Ein Audit-Log ist kein Luxus. Es ist das Rückgrat des Vertrauens in ein System.

-- Audit-Log Tabelle
CREATE TABLE audit_log (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  table_name  TEXT NOT NULL,
  record_id   UUID NOT NULL,
  action      TEXT NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')),
  old_data    JSONB,
  new_data    JSONB,
  changed_by  UUID REFERENCES users(id),
  changed_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Trigger-Funktion für automatisches Logging
CREATE OR REPLACE FUNCTION log_changes()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO audit_log (table_name, record_id, action, old_data, new_data, changed_by)
  VALUES (
    TG_TABLE_NAME,
    COALESCE(NEW.id, OLD.id),
    TG_OP,
    CASE WHEN TG_OP = 'INSERT' THEN NULL ELSE row_to_json(OLD)::JSONB END,
    CASE WHEN TG_OP = 'DELETE' THEN NULL ELSE row_to_json(NEW)::JSONB END,
    current_setting('app.current_user_id', true)::UUID
  );
  RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;

-- Trigger aktivieren
CREATE TRIGGER users_audit
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION log_changes();

Dieser Trigger schreibt bei jeder Änderung automatisch einen Eintrag. Er vergisst nichts. Er urteilt nicht. Er protokolliert.

Wichtig: Das Audit-Log selbst darf nicht veränderbar sein. Keine Updates, keine Deletes. Es ist ein append-only Protokoll. Wer das Audit-Log löschen kann, kann die Vergangenheit umschreiben.

DSGVO und Datenarchitektur: Zwei Welten, die zusammengehören

Die DSGVO ist kein Feind guter Architektur. Sie ist ein Spiegel. Sie zwingt dich, Fragen zu stellen, die du ohnehin stellen solltest: Warum speichere ich das? Wie lange brauche ich es? Wer darf es sehen?

Für KMU in Österreich bedeutet das konkret: Personenbezogene Daten brauchen ein Verfallsdatum, nicht nur im Datenschutzdokument, sondern in der Datenbank selbst.

-- Retention Policy: Nutzer-Daten nach 3 Jahren anonymisieren
UPDATE users
SET
  email      = 'anonymized_' || id || '@deleted.invalid',
  first_name = 'Gelöscht',
  last_name  = 'Gelöscht',
  phone      = NULL,
  deleted_at = NOW()
WHERE
  deleted_at IS NOT NULL
  AND deleted_at < NOW() - INTERVAL '3 years';

Das ist kein Hard Delete. Es ist Anonymisierung. Der Datensatz bleibt für referenzielle Integrität erhalten: Bestellungen, Logs und Verknüpfungen bleiben konsistent. Aber die Person dahinter ist nicht mehr rekonstruierbar.

Das ist der Unterschied zwischen Vergessen und Vernichten. Manchmal ist Vergessen genug.

Speicherbegrenzung ist keine technische Frage

Ich erlebe es regelmäßig: Ein System wächst. Die Datenbank wächst. Niemand fragt, warum. Niemand fragt, was davon noch gebraucht wird.

Eine Datenbank ist kein Archiv. Ein Archiv hat Regeln, wer wann was einsehen darf. Eine Datenbank ohne Governance ist ein Lager ohne Inventar. Du weißt, dass etwas drin ist, aber nicht mehr, was.

Für KMU ist das besonders kritisch. Kleine Teams, wenig Zeit für Datenpflege, schnell wachsende Tabellen. Die Lösung ist nicht mehr Speicher. Die Lösung ist Disziplin in der Datenmodellierung, von Anfang an.

Drei Fragen, die ich bei jedem neuen Datenmodell stelle:

  1. Wann darf dieser Datensatz gelöscht werden? Nicht "kann", sondern "darf". Rechtlich, fachlich, technisch.
  2. Wer muss informiert werden, wenn er gelöscht wird? Andere Systeme, andere Tabellen, andere Teams.
  3. Was passiert mit allem, was auf diesen Datensatz zeigt? Foreign Keys, Logs, Reports.

Diese Fragen sind unbequem. Sie verlangsamen den ersten Entwicklungstag. Sie verhindern den zehnten Notfall.

Was bleibt

Jede Zeile, die du in eine Datenbank schreibst, ist eine Entscheidung. Jede Zeile, die du nicht löschst, ist eine weitere Entscheidung. Und jede Zeile, die du löschst ohne zu wissen warum, ist ein Risiko.

Soft Delete oder Hard Delete: Das ist nicht die eigentliche Frage. Die eigentliche Frage ist: Hast du entschieden, was dieses System erinnern soll und was nicht? Oder hat es das stillschweigend für dich getan?

Eine Datenbank hat kein Gewissen. Das ist kein Mangel. Das ist eine Einladung.

Die Verantwortung, was vergessen wird, liegt bei dir.

§ · Häufige Fragen

Kurz und direkt beantwortet

01Was ist der Unterschied zwischen Soft Delete und Hard Delete?

Ein Hard Delete entfernt die Zeile endgültig aus der Datenbank: kein Recovery, kein Protokoll. Ein Soft Delete setzt nur eine Markierung, die den Datensatz aus der Oberfläche verschwinden lässt, während er physisch erhalten bleibt. Ein Soft Delete ist damit eine Architekturentscheidung, aber keine Datenschutzlösung.

02Erfüllt ein Soft Delete die DSGVO?

Nein. Wer Zugriff auf die Datenbank hat, sieht die als gelöscht markierten Daten weiterhin. Das Recht auf Vergessenwerden verlangt in bestimmten Fällen das tatsächliche Entfernen personenbezogener Daten. Ein üblicher Mittelweg ist Anonymisierung: Der Datensatz bleibt für die referenzielle Integrität erhalten, die Person dahinter ist aber nicht mehr rekonstruierbar.

03Welche drei Fragen sollte man vor dem Löschen klären?

Erstens: Wann darf dieser Datensatz gelöscht werden, rechtlich und fachlich? Zweitens: Wer muss informiert werden, wenn er gelöscht wird, also welche anderen Systeme und Tabellen hängen daran? Drittens: Was passiert mit allem, was auf diesen Datensatz zeigt, also Foreign Keys, Logs und Reports?

§ Weiterlesen

Verwandte Artikel.

Nächster Schritt

Du hast eine Idee oder ein konkretes Projekt? Lass uns unverbindlich darüber sprechen — Antwort innerhalb 24 Stunden.

Kontakt aufnehmenZurück zum Blog