it-swarm.com.de

Zyklen oder mehrere Kaskadenpfade mit On-Delete-Set null: wirklich?

Ich stelle diese Frage, um zu überprüfen, ob meine Argumentation zum kaskadierten Löschen richtig ist und ob ich nichts übersehen habe. Ich verstehe das Verhalten und frage nicht, warum es mit den aktuellen Einschränkungen implementiert wird.

Wenn wir versuchen, eine Tabelle mit einer Selbstreferenz wie dieser zu erstellen:

CREATE TABLE [BlogComments] (
    [Id] int NOT NULL IDENTITY,
    [AuthorName] nvarchar(100) NULL,
    [Content] nvarchar(max) NULL,
    [CreatedTime] datetime2 NOT NULL,
    [ReplyToId] int NULL,
    CONSTRAINT [PK_BlogComments] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_BlogComments_BlogComments_ReplyToId] FOREIGN KEY ([ReplyToId])
         REFERENCES [BlogComments] ([Id]) ON DELETE SET NULL -- Not: CASCADE
);

Wir bekommen die berüchtigte Ausnahme

Die Einführung der FOREIGN KEY-Einschränkung 'FK_BlogComments_BlogComments_ReplyToId' in der Tabelle 'BlogComments' kann zu Zyklen oder mehreren Kaskadenpfaden führen.

Ich verstehe, dass die Einstellung ON DELETE SET CASCADE Tatsächlich eine rekursive Kaskade von Löschvorgängen verursachen kann und daher gefährlich oder bestenfalls zeitaufwändig sein kann. Aber ON DELETE SET NULL Ist anders: Es macht nur ReplyToId von direkten untergeordneten Datensätzen ungültig, nicht von ihren untergeordneten Datensätzen.

Bin ich also zu Recht zu dem Schluss gekommen, dass SQL Server zu restriktiv ist, wenn der Fremdschlüssel mit ON DELETE SET NULL Definiert wird? Oder übersehe ich in diesem speziellen Fall einige gute Gründe für diese Einschränkung?

Übrigens ist mir eine Denkschule bekannt, die behauptet, dass es sollte sowieso keine Kaskadenaktionen geben . Lassen Sie uns nicht darauf eingehen.

6
Gert Arnold

Von Fehlermeldung 1785 tritt auf, wenn Sie eine FOREIGN KEY-Einschränkung erstellen, die mehrere Kaskadenpfade verursachen kann.

Sie erhalten diese Fehlermeldung, weil in SQL Server eine Tabelle nicht mehr als einmal in einer Liste aller kaskadierenden referenziellen Aktionen angezeigt werden kann, die entweder von einer DELETE- oder einer UPDATE-Anweisung gestartet werden. Beispielsweise darf der Baum der kaskadierenden referenziellen Aktionen nur einen Pfad zu einer bestimmten Tabelle im Baum der kaskadierenden referenziellen Aktionen haben.

Wenn eine Selbstreferenz mit on delete set null wurde von SQL Server zugelassen, dass der Abfrageplan zwei verschiedene Operatoren haben muss, die dieselbe Tabelle ändern. Zuerst die Zeilen löschen, die gelöschten Zeilen in einer Spool speichern und diese Spool verwenden, um die Zeilen zu finden, die aktualisiert werden müssen. Wenn Sie eine Zeile haben, in der ReplyId und Id identisch sind, wird versucht, den Wert einer bereits gelöschten Zeile zu aktualisieren. Oder zumindest, sobald die Transaktion festgeschrieben ist.

Ich sage nicht, dass es unmöglich ist, dies nur umzusetzen, weil es viel komplizierter ist, als man auf den ersten Blick denken könnte. Überlegen Sie, wie Sie mit Halloween-Schutz umgehen und dem Mix Trigger und indizierte Ansichten hinzufügen können, und es wird schnell zu etwas nicht Trivialem, den Plan zu erstellen, der die Aufgabe transaktionskorrekt erledigt.

4
Mikael Eriksson

Wenn diese Fehlermeldung nicht aufgetreten ist und ON DELETE SET NULL Wenn eine Spalte auf null gesetzt würde, hätten alle untergeordneten Zeilen, die auf den alten Wert verweisen, keine gültige übergeordnete Zeile mehr. Das Kaskadieren der Aktualisierung auf Null für alle untergeordneten Zeilen wäre erforderlich, um die Anforderungen an die relationale Integrität zu erfüllen. Eine Kaskadierung kann jedoch zu einer Endlosschleife führen. Dies ist das Bit, das nicht unterstützt wird. Dies hat nichts mit einer Selbstreferenz zu tun und ist nur mit der Möglichkeit, dass SQL Server einer Endlosschleife folgt.

Betrachten Sie dieses Setup:

DROP TABLE IF EXISTS dbo.CascadeDeletes;
GO

CREATE TABLE dbo.CascadeDeletes
(
    PrimaryID int NOT NULL
        CONSTRAINT PK_CascadeDeletes
        PRIMARY KEY CLUSTERED
    , SubordinateID int NULL
        CONSTRAINT FK_CascadeDeletes_SubID
        FOREIGN KEY
        REFERENCES dbo.CascadeDeletes (PrimaryID)
) ON [PRIMARY];

Fügen Sie einige Beispieldaten ein. effektiv ist jede Zeile das übergeordnete Element der nächsten Zeile.

INSERT INTO dbo.CascadeDeletes (PrimaryID, SubordinateID)
VALUES (1, NULL)
    , (2, 1)
    , (3, 2)
    , (4, 3);

Aktualisieren Sie jetzt die erste Zeile, sodass "Eltern" die letzte Zeile ist:

UPDATE dbo.CascadeDeletes
SET SubordinateID = 4
WHERE PrimaryID = 1;

Bedenken Sie, dass ein rekursiver CTE in dieser Tabelle in eine Endlosschleife eintreten würde:

;WITH rCTE AS
(
    SELECT cd.PrimaryID, cd.SubordinateID
    FROM dbo.CascadeDeletes cd
    WHERE cd.PrimaryID = 1
    UNION ALL
    SELECT ce.PrimaryID, ce.SubordinateID
    FROM dbo.CascadeDeletes ce
        INNER JOIN rCTE ON ce.SubordinateID = rcte.PrimaryID
)
SELECT *
FROM rCTE

Das Durchlaufen des Baums von primär zu untergeordnet ist jetzt eine Endlosschleife, die zu folgendem Fehler führt:

Nachricht 530, Ebene 16, Status 1, Zeile 27
Die Erklärung wurde beendet. Die maximale Rekursion 100 wurde vor Abschluss der Anweisung erschöpft.

Das Löschen einer Zeile aus der Tabelle wird unmöglich.

DELETE FROM dbo.CascadeDeletes
WHERE dbo.CascadeDeletes.PrimaryID = 1;

Nachricht 547, Ebene 16, Status 0, Zeile 27
Die DELETE-Anweisung stand in Konflikt mit der SAME TABLE REFERENCE-Einschränkung "FK_CascadeDeletes_SubID". Der Konflikt trat in der Datenbank "tempdb", Tabelle "dbo.CascadeDeletes", Spalte 'SubordinateID' auf.

Wenn SQL Server es uns erlauben würde, die Tabelle so zu definieren, dass ON DELETE SET NULL war in Kraft, das Löschen einer Zeile würde andere Zeilen, auf die durch die gelöschte Zeile verwiesen wird, auf NULL aktualisieren. Ganz klar, dies erzeugt keine "Endlosschleife", da immer nur ein einziger Satz von Zeilen aktualisiert wird. d.h. wenn die Zeile mit PrimaryID = 2 wird gelöscht, die einzige andere Zeile, die kaskadenaktualisiert werden soll, ist die Zeile mit PrimaryID = 3. Keine Endlosschleife.

Obwohl in unserem einfachen Beispiel keine Endlosschleife vorhanden ist, erwarte ich das CREATE TABLE und ALTER TABLE Codepfade erzeugen einen Fehler 1785 für jedes ON UPDATE oder ON DELETE Aktion, wenn das Muster so aussieht, als könnte es zyklisch sein, das ist nicht NO ACTION, nur um auf Nummer sicher zu gehen.

Man könnte dies als Fehler betrachten.

Glücklicherweise ist es jedoch ziemlich einfach, diesen Fehler mit der "alten Methode" zur Durchsetzung von RI zu umgehen. das ist mit einem Auslöser:

CREATE TRIGGER CascadeDeletes_OnDeleteSetNull
ON dbo.CascadeDeletes
INSTEAD OF DELETE
AS
BEGIN
    SET NOCOUNT ON;
    UPDATE dbo.CascadeDeletes
    SET SubordinateID = NULL
    FROM dbo.CascadeDeletes cd
        INNER JOIN deleted d ON cd.SubordinateID = d.PrimaryID;
    DELETE 
    FROM dbo.CascadeDeletes
    FROM dbo.CascadeDeletes cd
        INNER JOIN deleted d ON cd.PrimaryID = d.PrimaryID;
END

Wenn Sie nun eine Zeile aus unserer Tabelle löschen, wie in:

DELETE FROM dbo.CascadeDeletes
WHERE dbo.CascadeDeletes.PrimaryID = 1;

Die Zeile wird gelöscht und für verwandte Zeilen wird SubordinateID auf NULL gesetzt:

╔═══════════╦═══════════════╗ 
 ║ PrimaryID ║ SubordinateID ║ 
 ╠═════ ══════╬═══════════════╣ 
 ║ 2 ║ NULL ║ 
 ║ 3 ║ 2 ║ 
 ║ 4 ║ 3 ║ 
 ╚═══════════╩═══════════════╝
4
Max Vernon