Bevor ich eine Lösung vorstelle, möchte ich zunächst Ihre Annahme bestätigen:
Soweit ich sehen kann, besteht eine schemagebundene Abhängigkeit des Partitionsschemas (und der Funktion) von der Tabelle, die die Änderung des Datentyps verhindert.
Du hast Recht; Die Tatsache, dass Sie eine Partitionsfunktion für einen Datentyp von definiert haben, INT
hindert Sie daran, den Datentyp Ihrer Spalte zu ändern ... Leider bleiben Sie bei der aktuellen Tabelle hängen, da es meines Wissens keine Möglichkeit gibt, Anpassungen vorzunehmen Der Datentyp einer Partitionsfunktion, ohne sie zu löschen oder neu zu erstellen. Dies ist nicht möglich, da Tabellen davon abhängig sind. Im Grunde ist dies das Henne-Ei- Szenario.
Ich sehe keine Lösung für die Aktualisierung des Spaltentyps in dieser Datenbank, geschweige denn für eine Produktionsdatenbank. Wir können uns einige Wartungsausfälle leisten, z. B. + - 60 Minuten, aber nicht mehr. Was sind meine Optionen?
Eine mögliche Lösung für Ihr Problem besteht darin, die Vorteile partitionierter Ansichten zu nutzen . Diese Funktionalität gibt es schon immer und wird oft übersehen. Eine partitionierte Ansicht bietet Ihnen jedoch die Möglichkeit, Ihr Datenlimit zu umgehen, INT
indem Sie neue Daten zu einer anderen Tabelle hinzufügen, in der die ID
Spalte ein BIGINT
Datentyp ist. Dieser neuen Tabelle sollte auch eine bessere Partitionsfunktion / ein besseres Partitionsschema zugrunde liegen, und sie wird hoffentlich in Zukunft etwas besser beibehalten. Alle Abfragen, die auf die alte Tabelle verweisen, werden dann auf die partitionierte Ansicht verwiesen, und es tritt kein Datenlimitproblem auf.
Lassen Sie mich anhand eines Beispiels erklären. Das Folgende ist eine einfache Neuerstellung Ihrer aktuellen partitionierten Tabelle, die aus einer Partition besteht, wie folgt:
-- Create your very frustraiting parition scheme
CREATE PARTITION FUNCTION dear_god_why_this_logic (INT)
AS RANGE RIGHT FOR VALUES (0);
-- Scheme to store data on PartitionedData Filegroup
CREATE PARTITION SCHEME dear_god_why_this_scheme
AS PARTITION dear_god_why_this_logic ALL TO ([PRIMARY]);
-- Create Partitioned Table
CREATE TABLE dbo.TestPartitioned
(
ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
VAL CHAR(1)
) ON dear_god_why_this_scheme (ID);
--Populate Table with Data
INSERT INTO dbo.TestPartitioned WITH (TABLOCK) (VAL)
SELECT CHAR(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % 128)
FROM master.sys.configurations t1
CROSS JOIN master.sys.configurations t2
CROSS JOIN master.sys.configurations t3;
Nehmen wir als Argument an, dass ich mich dem INT
Limit nähere (obwohl ich es offensichtlich nicht bin). Da mir nicht die gültigen INT
Werte in der ID
Spalte ausgehen sollen, werde ich eine ähnliche Tabelle erstellen, sondern stattdessen BIGINT
für die ID
Spalte verwenden. Diese Tabelle wird wie folgt definiert:
CREATE PARTITION FUNCTION lets_maintain_this_going_forward (BIGINT)
AS RANGE RIGHT FOR VALUES (0, 500000, 1000000, 1500000, 2000000);
-- Scheme to store data on PartitionedData Filegroup
CREATE PARTITION SCHEME lets_maintain_this_going_forward_scheme
AS PARTITION lets_maintain_this_going_forward ALL TO ([PartitionedData]);
-- Table for New Data going forward with new ID datatype of BIGINT
CREATE TABLE dbo.TestPartitioned_BIGINT
(
ID BIGINT IDENTITY(500000,1) PRIMARY KEY CLUSTERED,
VAL CHAR(1)
) ON lets_maintain_this_going_forward_scheme (ID);
-- Add check constraint to be used by Partitioned View
ALTER TABLE dbo.TestPartitioned_BIGINT ADD CONSTRAINT CK_ID_BIGINT CHECK (ID > CAST(499999 AS BIGINT));
Ein paar Anmerkungen hier:
Tabellenpartitionierung in der neuen Tabelle
Diese neue Tabelle wird auch entsprechend Ihren Anforderungen partitioniert. Es ist eine gute Idee, über zukünftige Wartungsanforderungen nachzudenken. Erstellen Sie daher einige neue Dateigruppen, definieren Sie eine bessere Strategie für die Partitionsausrichtung usw. Mein Beispiel ist, es einfach zu halten, sodass ich alle meine Partitionen in eine Dateigruppe zusammenfasse. Tun Sie dies nicht in der Produktion, sondern befolgen Sie stattdessen die Best Practices für die Tabellenpartitionierung mit freundlicher Genehmigung von Brent Ozar et. al.
Überprüfen Sie die Einschränkung
Da ich partitionierte Ansichten nutzen möchte, muss ich CHECK CONSTRAINT
dieser neuen Tabelle eine hinzufügen . Ich weiß, dass meine Einfügeanweisung ~ 440.000 Datensätze generiert hat. Um sicher zu gehen, werde ich meinen IDENTITY
Startwert bei 500.000 beginnen und auch eine CONSTRAINT
Definition erstellen . Die Einschränkung wird vom Optimierer verwendet, wenn bewertet wird, welche Tabellen beim Aufrufen der eventuellen partitionierten Ansicht entfernt werden können.
Nun die beiden Tische zusammen pürieren
Partitionierte Ansichten eignen sich nicht besonders gut, wenn Sie gemischte Datentypen auf sie werfen, wenn es um die Partitionsspalte in den zugrunde liegenden Tabellen geht. Um dies zu umgehen, müssen wir die aktuelle ID
Spalte in Ihrer aktuellen Tabelle als BIGINT
Wert beibehalten. Dazu fügen wir eine persistierte berechnete Spalte wie folgt hinzu:
-- Append Calculated Columns of New Datatype to Old Table
-- WARNING: This will take a while against a large data set and will likely lock the table
ALTER TABLE dbo.TestPartitioned ADD ID_BIG AS (CAST(ID AS BIGINT)) PERSISTED
GO
-- Add Constraints on Calculated Column
-- WARNING: This will likely lock the table
ALTER TABLE dbo.TestPartitioned ADD CONSTRAINT CK_ID_TestPartitioned_BIGINT CHECK(ID_BIG <= CAST(499999 AS BIGINT));
GO
-- Create a new Nonclustered index on ID_BIG to act as new "pkey"
CREATE NONCLUSTERED INDEX IX_TestPartitioned__BIG_ID__VAL ON dbo.TestPartitioned (ID_BIG) INCLUDE (VAL);
Ich habe auch eine andere hinzugefügt CHECK CONSTRAINT
auf dem alten Tisch (um Hilfe mit Partition Elimination von unserem eventuellen partitionierten View) und einem neuen Non-Clustered Index, der wird wirken wie ein Primärschlüsselindex (weil Look - Ups mit Ursprung aus der partitionierte Sicht gehen auftreten ID_BIG
statt ID
).
Mit der neuen berechneten Spalten- und Prüfbeschränkung kann ich die partitionierte Ansicht schließlich wie folgt definieren:
-- Build a Partitioned View on Top of the old and new tables
CREATE VIEW dbo.vw_TableAll
WITH SCHEMABINDING
AS
SELECT ID_BIG AS ID, VAL FROM dbo.TestPartitioned
UNION ALL
SELECT ID, VAL FROM dbo.TestPartitioned_BIGINT
GO
Wenn Sie eine schnelle Abfrage für diese Ansicht ausführen, wird bestätigt, dass die Partitionseliminierung funktioniert (da für die neue Tabelle, die wir erstellt haben, keine Suchvorgänge auftreten):
SELECT *
FROM dbo.vw_TableAll
WHERE ID < CAST(500 AS BIGINT);
Ausführungsplan:
Zu diesem Zeitpunkt müssen Sie einige Änderungen an Ihrer Anwendung vornehmen:
- Beenden Sie das Einfügen neuer Datensätze in die alte Tabelle und fügen Sie sie stattdessen in die neue Tabelle ein. Wir dürfen keine Datensätze in die partitionierte Ansicht einfügen, da wir
IDENTITY
Werte in den zugrunde liegenden Tabellen verwenden. Partitionierte Ansichten lassen dies nicht zu , daher müssen Sie in diesem Szenario Datensätze direkt in die neue Tabelle einfügen.
- Passen Sie alle
SELECT
Abfragen (die auf die alte Tabelle verweisen) so an, dass sie auf die partitionierte Ansicht verweisen. Alternativ können Sie die alte Tabelle umbenennen (z. B. TableName_Old) und die Ansicht als Namen der alten Tabellen erstellen (z. B. TableName). Wie ausgefallen Sie hierher kommen möchten, liegt ganz bei Ihnen.
Neue Datensätze in die neue Tabelle
Zu diesem Zeitpunkt sollten neue Datensätze in die neue Tabelle eingefügt werden. Ich werde dies simulieren, indem ich Folgendes ausführe:
--Populate Table with Data
INSERT INTO dbo.TestPartitioned_BIGINT WITH (TABLOCK) (VAL)
SELECT CHAR(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % 128)
FROM master.sys.configurations t1
CROSS JOIN master.sys.configurations t2
CROSS JOIN master.sys.configurations t3;
GO
Auch hier ist mein Identity Seed so konfiguriert, dass keine ID-Konflikte zwischen den beiden Tabellen auftreten. Die CHECK CONSTRAINTS
auf beiden Tabellen sollten dies ebenfalls erzwingen. Lassen Sie uns bestätigen, dass die Partitionseliminierung immer noch stattfindet:
SELECT *
FROM dbo.vw_TableAll
WHERE ID > CAST(300000 AS BIGINT)
AND ID < CAST(300500 AS BIGINT);
SELECT *
FROM dbo.vw_TableAll
WHERE ID > CAST(500000 AS BIGINT)
AND ID < CAST(500500 AS BIGINT);
Ausführungspläne:
Beachten Sie, dass die meisten Abfragen wahrscheinlich beide Tabellen umfassen. In diesen Szenarien können wir die Partitionseliminierung zwar nicht nutzen, die Abfragepläne sollten jedoch so optimal wie möglich bleiben, mit dem Vorteil, dass Sie Ihre zugrunde liegenden Abfragen nicht neu schreiben müssen (wenn Sie die Ansicht gleich benennen möchten) wie dein alter Tisch).
Was nun?
Nun, das hängt ganz von dir ab. Wenn die aktuellen Rekorde niemals verschwinden und Sie mit der Leistung von Partitioned View zufrieden sind, gießen Sie ein festliches Bier ein, weil Sie fertig sind. Huzzah!
Wenn Sie die alte Tabelle in der neuen Tabelle konsolidieren möchten, müssen Sie Wartungsfenster auswählen, in denen die nächste Reihe von Vorgängen ausgeführt werden soll. Grundsätzlich werden Sie die Einschränkungen für die Tabellen löschen (wodurch die Partitionseliminierungskomponente der partitionierten Ansicht beschädigt wird), Ihre neuesten Datensätze von der alten Tabelle in die neue Tabelle kopieren und diese Datensätze aus der alten Tabelle löschen. und aktualisieren Sie dann die Einschränkungen (damit die partitionierte Ansicht die Partitionseliminierung erneut nutzen kann). Aufgrund des Datenvolumens in den vorhandenen Tabellen müssen Sie möglicherweise einige Runden dieses Prozesses durchlaufen, um alles zu konsolidieren. Diese Schritte sind wie folgt zusammengefasst:
-- During a maintenance window, transfer old records to new table if you so choose
-- Drop Check Constraint while transferring over records
ALTER TABLE dbo.TestPartitioned_BIGINT DROP CONSTRAINT CK_ID_BIGINT;
-- Retain Identity Values
SET IDENTITY_INSERT dbo.TestPartitioned_BIGINT ON
-- Copy records into the new table
INSERT INTO dbo.TestPartitioned_BIGINT (ID, VAL)
SELECT ID_BIG, VAL
FROM dbo.TestPartitioned
WHERE ID > 300000
SET IDENTITY_INSERT dbo.TestPartitioned_BIGINT OFF
-- Enable Check Constraint after transferred records are complete, ensuring the Check Constraint is defined on the proper ID value
ALTER TABLE dbo.TestPartitioned_BIGINT ADD CONSTRAINT CK_ID_BIGINT CHECK (ID > CAST(300000 AS BIGINT));
GO
-- Purge records from original table
DELETE
FROM dbo.TestPartitioned
WHERE ID > 300000
-- Recreate Check Constraint on original table, ensuring the Check Constraint is defined on the proper ID value
ALTER TABLE dbo.TestPartitioned DROP CONSTRAINT CK_ID_TestPartitioned_BIGINT;
GO
ALTER TABLE dbo.TestPartitioned ADD CONSTRAINT CK_ID_TestPartitioned_BIGINT CHECK(ID_BIG <= CAST(300000 AS BIGINT));
GO
Wenn möglich, testen Sie dies in einer Umgebung ohne Produktion. Ich kann Tests in der Produktion nicht gutheißen. Hoffentlich hilft diese Antwort, und wenn Sie Fragen haben, können Sie gerne einen Kommentar schreiben, und ich werde mein Bestes tun, um mich schnell bei Ihnen zu melden.