Ändern des Datentyps der Primärschlüsselspalten partitionierter Tabellen

7

Ich bin derzeit mit der folgenden Situation gesegnet:

  • Wir haben eine Produktionsdatenbank, die partitioniert ist und Primärschlüssel vom Typ verwendet int
  • Mehrere (~ 20) Tabellen mit jeweils> 100.000.000 Zeilen, von denen 2 in der Nähe von 2.000.000.000 Zeilen liegen
  • Diese Tabellen haben alle eine Spalte vom Typ intals Primärschlüssel, die in jeder Zeile um eins erhöht wird
  • Diese Tabellen sind aufgeteilt, eine Partitionsfunktion des Typs verwendet int, unter Verwendung RIGHT(0), so effektiv alle Daten in dem einen und einzigen Partition

Da sich einige Tabellen dem int32Maximalwert nähern, müssen wir diese Spaltentypen in ändern bigint. Hier beginnen die Probleme:

  • Der Typ einer Primärschlüsselspalte kann nicht geändert werden (dies kann ich vermeiden, indem ich den Primärschlüssel lösche, den Typ ändere und den Primärschlüssel erneut erstelle). Ich kann den Spaltentyp jedoch nach dem Löschen der Primärschlüsseleinschränkung nicht ändern, indem ich die folgende Ausnahme:

    {"Das Objekt 'XXX_XXX' ist abhängig von der Spalte 'ABCD'. ALTER TABLE ALTER COLUMN ABCD ist fehlgeschlagen, weil ein oder mehrere Objekte auf diese Spalte zugreifen."}

Soweit ich sehen kann, besteht eine schemagebundene Abhängigkeit des Partitionsschemas (und der Funktion) von der Tabelle, die die Änderung des Datentyps verhindert. Es gibt keine Fremdschlüssel, die auf diese Primärschlüssel verweisen.

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?

Zusätzliche Information

  • Ich fange an zu glauben, dass meine einzige Möglichkeit darin besteht, neue Tabellen mit der richtigen Struktur zu erstellen und die vorhandenen Tabellendaten in die neuen zu pumpen ...

  • Tabellen sind bereits anhand ihres Primärschlüsselwerts partitioniert (derzeit nur 1 Partition ( RIGHT(0)zukünftige Partitionen sollten für neue Werte möglich sein ( RIGHT(x)), die Grenze muss xnoch festgelegt werden).

  • Dies sind Datenprotokollierungstabellen, nur Einfügungen, keine Aktualisierungen oder Löschungen

  • Auf diesen Tabellen wird immer DML ausgeführt, mit Ausnahme von Wartungsfenstern, die alle 6-8 Wochen etwa 90 Minuten lang ausgeführt werden.

  • Die auf dem Primärschlüssel basierenden Indizes sind alle gruppiert

  • Diese Tabellen enthalten keine nicht gruppierten Indizes

Allmächtig
quelle
Adimeus .... mit der Partitionstabelle und der zugehörigen Funktion müssen Sie 1) die Funktion von der Tabelle trennen, 2) die PK löschen, 3) die PK erneut auf den gewünschten Datentyp eingeben, 4) ändern / Bringen Sie den Part-Funct wieder an der Tabelle an. Es gibt keine einfache Möglichkeit, dies durchzuführen, ohne die Teilfunktion und die Tabelle zu löschen und beide von Grund auf neu zu erstellen.
GoldBishop
Adimeus ... es ist auch unklug, eine PK für eine Teilfunktion zu verwenden. In den meisten Geschäftsfällen werden Teilfunktionen in Datums- / Zeitfeldern zur Segmentierung von Jahresdaten verwendet. In seltenen Fällen habe ich gesehen, dass sie in Smallint-Feldern verwendet werden, um nach Typcode-Codes zu partitionieren. Andernfalls klingt es so, als ob Ihre "Begabung" besser mit Indizes oder Schemaansichten bedient werden könnte.
GoldBishop

Antworten:

5

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, INThindert 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, INTindem Sie neue Daten zu einer anderen Tabelle hinzufügen, in der die IDSpalte ein BIGINTDatentyp 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 INTLimit nähere (obwohl ich es offensichtlich nicht bin). Da mir nicht die gültigen INTWerte in der IDSpalte ausgehen sollen, werde ich eine ähnliche Tabelle erstellen, sondern stattdessen BIGINTfür die IDSpalte 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 CONSTRAINTdieser neuen Tabelle eine hinzufügen . Ich weiß, dass meine Einfügeanweisung ~ 440.000 Datensätze generiert hat. Um sicher zu gehen, werde ich meinen IDENTITYStartwert bei 500.000 beginnen und auch eine CONSTRAINTDefinition 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 IDSpalte in Ihrer aktuellen Tabelle als BIGINTWert 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 CONSTRAINTauf 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_BIGstatt 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:

Geben Sie hier die Bildbeschreibung ein

Zu diesem Zeitpunkt müssen Sie einige Änderungen an Ihrer Anwendung vornehmen:

  1. 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 IDENTITYWerte 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.
  2. Passen Sie alle SELECTAbfragen (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 CONSTRAINTSauf 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:

Geben Sie hier die Bildbeschreibung ein

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.

John Eisbrener
quelle