Zum Inhalt springen

Soft Deletes löschen nicht, und das ist das Problem

Eine Zeile, und jede Unique-Constraint, jeder Fremdschlüssel und jede Löschanfrage bedeuten etwas anderes. Die meisten Codebasen übernehmen den Trait, ohne davon etwas zu entscheiden.

4 Min. Lesezeit

SoftDeletes zu einem Modell hinzuzufügen dauert eine Zeile, und jedes Tutorial stellt es als kostenloses Rückgängigmachen dar. Ist es nicht. Es verändert die Bedeutung von Löschen in der ganzen Anwendung, und die Folgen landen an Stellen, die mit dem Modell, dem Sie es hinzugefügt haben, nichts zu tun haben.

Was tatsächlich passiert

delete() hört auf zu löschen. Es setzt deleted_at und die Zeile bleibt. Ein globaler Scope versteckt sie vor Abfragen. Das ist der gesamte Mechanismus, und jedes Problem unten folgt daraus, dass die Zeile noch da ist.

Unique-Constraints hören auf zu funktionieren

Ein Nutzer registriert sich mit einer Adresse, löscht sein Konto und versucht sich mit derselben Adresse erneut zu registrieren. Die Zeile steht noch in der Tabelle, der Unique-Index liegt noch auf der Spalte, und die Registrierung scheitert mit einem Datenbankfehler über einen Datensatz, den der Nutzer nicht sehen und den der Support nicht finden kann.

Die Anwendung sagt, das Konto existiert nicht. Die Datenbank sagt, es existiert. Beide sagen die Wahrheit über verschiedene Dinge.

Zwei Auswege, und es sind Entscheidungen statt Behebungen:

// eine lebende Zeile, beliebig viele gelöschte
$table->unique(['email', 'deleted_at']);

Oder den Wert beim Löschen freigeben, durch Umschreiben:

public function delete(): bool
{
    $this->email = "{$this->email}#deleted-{$this->id}";
    $this->save();
 
    return parent::delete();
}

Das Erste behält den ursprünglichen Wert und kann Eindeutigkeit über lebende und gelöschte Zeilen hinweg nicht durchsetzen. Das Zweite gibt die Adresse frei und verliert das Original – was zählt, wenn Sie je wissen müssen, wem dieses Konto gehörte. Keines ist falsch; keines zu wählen schon.

Beziehungen kommen falsch aussehend zurück

Ein soft-gelöschter Elterndatensatz erfüllt weiterhin seine Fremdschlüssel, denn die Zeile existiert. Eine Kindzeile ist in der Datenbank also nicht verwaist, und ein Join, der von Soft Deletes nichts weiß, liefert bereitwillig den gelöschten Elternteil zurück.

Eloquents Scope versteckt ihn, wenn Sie das Modell abfragen. Rohe Abfragen, von Hand geschriebene Reports und selbstgebaute Aggregate tun das nicht – und genau dort hören zwei Bildschirme auf, sich über dieselbe Zahl einig zu sein.

Schlimmer noch: withTrashed() auf einer Seite einer Beziehung und nicht auf der anderen ergibt ein Ergebnis, das in keiner Richtung korrekt ist.

Löschung ist nicht Löschen

Eine Person übt ihr Recht auf Löschung aus. Die Anwendung ruft delete() auf. Die Zeile ist noch da, noch lesbar für jeden mit Datenbankzugriff, noch in jedem Backup.

Nichts wurde gelöscht. Die rechtliche Pflicht ist nicht erfüllt, und das System meldet, dass sie es sei – was schlimmer ist als lautes Scheitern.

Ein Löschpfad muss den Mechanismus bewusst umgehen:

$user->forceDelete();

Oder, wo Datensätze aus steuerlichen oder Prüfungsgründen bleiben müssen, anonymisieren statt entfernen – die personenbezogenen Felder ersetzen, die Zeile behalten und festhalten, dass es passiert ist. Das ist eine Entscheidung mit einem Juristen darin, und der Code hat umzusetzen, welche Antwort auch kommt, statt den Standard zu nehmen.

Die Tabelle wächst nur

Nichts räumt soft-gelöschte Zeilen weg, solange niemand die Aufgabe schreibt. Jahre später besteht die Tabelle zu einem erheblichen Teil aus Zeilen, die niemand sehen kann, Indizes sind größer als nötig, und jede Abfrage trägt ein deleted_at is null mit, das der Planer berücksichtigen muss.

Wenn der Trait auf einem Modell sitzt, sollte es eine Aufbewahrungsregel geben und eine Aufgabe, die sie durchsetzt. „Wir behalten alles für immer" ist eine gültige Regel; keine Regel zu haben ist der Weg, auf dem eine Tabelle vierzig Millionen Zeilen erreicht, von denen sechs Millionen zählen.

Wann er wirklich richtig ist

Wo Rückgängigmachen eine Funktion ist, die Nutzer erwarten – ein Archiv, ein Papierkorb, eine versehentliche Löschung, die der Support umkehren können soll.

Wo der Datensatz von Historie referenziert wird, die lesbar bleiben muss: Eine Rechnung, die einen inzwischen entfernten Kunden nennt, sollte ihn weiterhin nennen.

Wo Löschen ein Arbeitsablauf ist statt eines Ereignisses, mit einem Freigabeschritt zwischen Markieren und Entfernen.

Das ist eine engere Menge als „jedes Modell", und dort landet der Trait üblicherweise. Der Standard sollte ein echtes Löschen sein, und der Trait dorthin kommen, wo jemand sagen kann, was Rückgängigmachen für diese Tabelle bedeutet – und wo jemand die Frage zur Unique-Constraint, die Frage zum Reporting und die Frage zur Aufbewahrung beantwortet hat, bevor die erste Zeile gelöscht wird, und nicht nach dem ersten Support-Ticket.

Unique-Constraints und Auswertungen sind Schemafragen. Beides nachträglich in eine Tabelle einzuziehen, die seit zwei Jahren weich löscht, ist Datenarbeit und dauert länger, als man denkt. An der Löschfrage hängt eine gesetzliche Frist, sie gehört also zu den anderen Entscheidungen, die sich nicht zurücknehmen lassen.

Verwandte Fragen

Sollten wir Soft Deletes standardmäßig verwenden?
Nein - der Standard sind harte Löschungen, und der Trait kommt dorthin, wo es einen Grund gibt. Der Grund ist meist, dass jemand rückgängig machen können muss, oder dass der Datensatz von Historie referenziert wird, die lesbar bleiben muss. Aus Gewohnheit überall angewandt, macht er jede Tabelle zu einem Archiv, das niemand aufräumt, und jede Constraint zu einer Halbwahrheit.
Wie geht man mit Unique-Spalten um?
Entweder nehmen Sie den Löschzeitstempel in den Unique-Index auf, was eine lebende Zeile plus beliebig viele gelöschte erlaubt, oder Sie hören auf, den natürlichen Wert als Schlüssel zu verwenden, und geben ihn beim Löschen durch Umschreiben frei. Beides sind bewusste Entscheidungen; was nicht funktioniert, ist ein schlichter Unique-Index auf einer soft-löschenden Tabelle.
Erfüllt ein Soft Delete eine Löschanfrage?
Nein, und das ist die mit rechtlicher statt technischer Folge. Die Daten sind weiterhin da und weiterhin lesbar, also wurde nichts gelöscht. Eine Löschanfrage braucht echtes Entfernen oder echte Anonymisierung, wobei der Soft-Delete-Mechanismus ausdrücklich umgangen wird.
Was ist mit den Zeilen, die auf die gelöschte zeigen?
Sie zeigen weiter darauf, denn die Zeile ist noch da und der Fremdschlüssel weiterhin erfüllt. Das ist oft genau das, was Sie wollten - eine alte Rechnung nennt weiterhin ihren Kunden - und es ist eine Entscheidung je Beziehung statt einer Eigenschaft des Traits.

← Zurück zu allen Artikeln

Anrufen+1 848 272 7583WhatsApp+90 850 308 5436E-Mailinfo@codefacture.comKontaktseite