Bei dem Versuch, eine Anwendung von unserer monolithischen Datenbank zu entkoppeln, haben wir versucht, die INT IDENTITY-Spalten verschiedener Tabellen in eine PERSISTED-berechnete Spalte zu ändern, die COALESCE verwendet. Grundsätzlich benötigen wir für die entkoppelte Anwendung die Möglichkeit, die Datenbank für gemeinsame Daten, die von vielen Anwendungen gemeinsam genutzt werden, zu aktualisieren, während vorhandene Anwendungen weiterhin Daten in diesen Tabellen erstellen können, ohne dass Code- oder Prozeduränderungen erforderlich sind.
Im Wesentlichen haben wir uns von einer Spaltendefinition von entfernt.
PkId INT IDENTITY(1,1) PRIMARY KEY
zu;
PkId AS AS COALESCE(old_id, external_id, new_id) PERSISTED NOT NULL,
old_id INT NULL, -- Values here are from existing records of PkId before table change
external_id INT NULL,
new_id INT IDENTITY(2000000,1) NOT NULL
In allen Fällen ist die PkId auch ein PRIMARY KEY und in allen bis auf einen Fall ist sie CLUSTERED. Alle Tabellen haben dieselben Fremdschlüssel und Indizes wie zuvor. Im Wesentlichen ermöglicht das neue Format die Bereitstellung der PkId durch die entkoppelte Anwendung (als external_id), ermöglicht jedoch auch, dass die PkId der IDENTITY-Spaltenwert ist, sodass vorhandener Code, der auf der IDENTITY-Spalte basiert, mithilfe von SCOPE_IDENTITY und @@ IDENTITY verwendet wird arbeiten wie früher.
Das Problem, das wir hatten, ist, dass wir auf einige Abfragen gestoßen sind, die früher in einer akzeptablen Zeit ausgeführt wurden, um jetzt vollständig zu verschwinden. Die generierten Abfragepläne, die von diesen Abfragen verwendet werden, sind nicht mehr so wie früher.
Angesichts der Tatsache, dass die neue Spalte ein PRIMARY KEY mit demselben Datentyp wie zuvor und PERSISTED ist, hätte ich erwartet, dass sich Abfragen und Abfragepläne genauso verhalten wie zuvor. Sollte sich die COMPUTED PERSISTED INT PkId im Wesentlichen genauso verhalten wie eine explizite INT-Definition in Bezug darauf, wie SQL Server den Ausführungsplan erstellt? Gibt es andere wahrscheinliche Probleme mit diesem Ansatz, die Sie sehen können?
Der Zweck dieser Änderung sollte es uns ermöglichen, die Tabellendefinition zu ändern, ohne vorhandene Prozeduren und Code ändern zu müssen. Angesichts dieser Probleme habe ich nicht das Gefühl, dass wir diesen Ansatz verfolgen können.
quelle
Antworten:
ZUERST
Sie müssen wahrscheinlich alle drei Spalten nicht:
old_id
,external_id
,new_id
. Alsnew_id
SpalteIDENTITY
wird für jede Zeile ein neuer Wert generiert, auch wenn Sie in einfügenexternal_id
. Aber zwischenold_id
und schließenexternal_id
sich diese so ziemlich gegenseitig aus: Entweder gibt es bereits einenold_id
Wert, oder diese Spalte wird in der aktuellen Konzeption nur verwendet,NULL
wennexternal_id
oder verwendet wirdnew_id
. Da Sie einer bereits vorhandenen Zeile (dh einer mit einemold_id
Wert) keine neue "externe" ID hinzufügen und keine neuen Werte eingehenold_id
, kann eine Spalte verwendet werden für beide Zwecke.Entfernen Sie also die
external_id
Spalte und benennen Sieold_id
sie in etwas Ähnlichesold_or_external_id
oder was auch immer um. Dies sollte keine wirklichen Änderungen an irgendetwas erfordern, reduziert jedoch einige der Komplikationen. Möglicherweise müssen Sie die Spalte höchstens aufrufenexternal_id
, auch wenn sie "alte" Werte enthält, wenn bereits App-Code zum Einfügen geschrieben wurdeexternal_id
.Das reduziert die neue Struktur auf gerecht:
Jetzt haben Sie nur 8 Bytes pro Zeile anstelle von 12 Bytes hinzugefügt (vorausgesetzt, Sie verwenden die
SPARSE
Option oder Datenkomprimierung nicht). Und Sie mussten keinen Code, T-SQL oder App-Code ändern.ZWEITE
Wenn wir diesen Weg der Vereinfachung fortsetzen, schauen wir uns an, was wir noch übrig haben:
old_or_external_id
Spalte enthält entweder bereits Werte oder erhält von der App einen neuen Wert oder wird als belassenNULL
.new_id
wird immer ein neuer Wert generiert, aber dieser Wert wird nur verwendet, wenn dieold_or_external_id
Spalte istNULL
.Es gibt nie eine Zeit, in der Sie Werte in
old_or_external_id
und benötigen würdennew_id
. Ja, es wird Zeiten geben, in denen beide Spalten Werte haben, weilnew_id
sie ein sindIDENTITY
, aber diesenew_id
Werte werden ignoriert. Auch diese beiden Felder schließen sich gegenseitig aus. So was nun?Jetzt können wir untersuchen, warum wir das überhaupt brauchten
external_id
. In Anbetracht der Tatsache, dass das Einfügen in eineIDENTITY
Spalte mithilfe von möglich istSET IDENTITY_INSERT {table_name} ON;
, können Sie keine Schemaänderungen vornehmen und nur Ihren App-Code ändern, um dieINSERT
Anweisungen / OperationenSET IDENTITY_INSERT {table_name} ON;
undSET IDENTITY_INSERT {table_name} OFF;
Anweisungen einzuschließen. Sie müssen dann bestimmen, auf welchen Startbereich dieIDENTITY
Spalte zurückgesetzt werden soll (für neu generierte Werte), da dieser deutlich über den Werten liegen muss, die der App-Code einfügt, da das Einfügen eines höheren Werts den nächsten automatisch generierten Wert verursacht größer sein als der aktuelle MAX-Wert. Sie können jedoch jederzeit einen Wert einfügen, der unter dem Wert IDENT_CURRENT liegt.Das Kombinieren der Spalten
old_or_external_id
undnew_id
erhöht nicht auch die Wahrscheinlichkeit, dass eine automatisch überlappende Wertsituation zwischen automatisch generierten Werten und von der App generierten Werten auftritt, da die Absicht besteht, die Spalten 2 oder sogar 3 zu einem Primärschlüsselwert zu kombinieren. und das sind immer eindeutige Werte.Bei diesem Ansatz müssen Sie nur:
Lassen Sie die Tabellen wie folgt:
Dies fügt jeder Zeile 0 Bytes hinzu, anstatt 8 oder sogar 12.
SET IDENTITY_INSERT {table_name} ON;
undSET IDENTITY_INSERT {table_name} OFF;
Anweisungen.ZWEITENS, Teil B.
Eine Variation des direkt oben erwähnten Ansatzes wäre, dass der App-Code Werte einfügt, die mit -1 beginnen und von dort nach unten gehen . Dadurch bleiben die
IDENTITY
Werte als die einzigen , die gehen nach oben . Der Vorteil hierbei ist, dass Sie nicht nur das Schema nicht komplizieren, sondern sich auch keine Gedanken über überlappende IDs machen müssen (wenn die von der App generierten Werte in den neuen automatisch generierten Bereich laufen). Dies ist nur eine Option, wenn Sie noch keine negativen ID-Werte verwenden (und es scheint ziemlich selten zu sein, dass Benutzer negative Werte für automatisch generierte Spalten verwenden, sodass dies in den meisten Situationen wahrscheinlich sein sollte).Bei diesem Ansatz müssen Sie nur:
Lassen Sie die Tabellen wie folgt:
Dies fügt jeder Zeile 0 Bytes hinzu, anstatt 8 oder sogar 12.
-1
.SET IDENTITY_INSERT {table_name} ON;
undSET IDENTITY_INSERT {table_name} OFF;
Anweisungen.Hier müssen Sie noch
IDENTITY_INSERT
Folgendes tun , aber: Sie fügen keine neuen Spalten hinzu, müssen keineIDENTITY
Spalten "neu säen" und haben kein zukünftiges Risiko von Überlappungen.ZWEITENS, Teil 3
Eine letzte Variante dieses Ansatzes wäre, möglicherweise die
IDENTITY
Spalten auszutauschen und stattdessen Sequenzen zu verwenden . Der Grund für diesen Ansatz besteht darin, dass der App-Code folgende Werte einfügen kann: positiv, über dem automatisch generierten Bereich (nicht unter) und nicht erforderlichSET IDENTITY_INSERT ON / OFF
.Bei diesem Ansatz müssen Sie nur:
Kopieren Sie die
IDENTITY
Spalte in eine neue Spalte, die nicht über dieIDENTITY
Eigenschaft verfügt, jedoch eineDEFAULT
Einschränkung aufweist, indem Sie die Funktion NEXT VALUE FOR verwenden:Dies fügt jeder Zeile 0 Bytes hinzu, anstatt 8 oder sogar 12.
SET IDENTITY_INSERT {table_name} ON;
undSET IDENTITY_INSERT {table_name} OFF;
Anweisungen.JEDOCH , aufgrund der Anforderung , dass Code entweder mit
SCOPE_IDENTITY()
oder@@IDENTITY
nach wie vor einwandfrei funktioniert, um Sequenzen Schalt ist nicht eine Option , da es scheint , dass es keine Entsprechung dieser Funktionen für Sequenzen ist :-(. Sad!quelle
IDENTITY_INSERT
, diese aber nicht getestet. Sie sind sich nicht sicher, ob Option 1 Ihr Gesamtproblem lösen wird. Es war nur eine Beobachtung, um unnötige Komplexität zu reduzieren. Wie können Sie jedoch sicherstellen, dass mehrere Threads, die neue "externe" IDs einfügen, eindeutig sind?IDENTITY_INSERT ON
für die gleiche Tabelle in zwei Sitzungen und wurde ohne Probleme in beide eingefügt.