Was ist der schnellste Weg, um Daten zu löschen?

18

Szenario:

Wir haben zwei Tabellen Tbl1& Tbl2auf dem Subscriber Server. Die Tbl1wird von Publisher repliziert Server Aund hat zwei Trigger - Einfügen und Aktualisieren. Die Trigger fügen die Daten ein und aktualisieren sie Tbl2.

Jetzt müssen wir Tbl2löschen (ca. 900 Millionen Datensätze), von denen insgesamt mehr als 1000 Millionen Datensätze vorliegen. Nachfolgend finden Sie die Datenverteilung für einen Monat bis zu einer Minute.

  • Ein Monat - 14986826 Zeilen
  • Ein Tag - 483446 Zeilen
  • Eine Stunde - 20143 Zeilen
  • Eine Minute - 335 Zeilen

Was ich suche;

Der schnellste Weg, um diese Daten ohne Produktionsprobleme, Datenkonsistenz und möglicherweise ohne Ausfallzeiten zu bereinigen. Also, ich denke, die folgenden Schritte zu befolgen, aber stecken :(

Schritte:

  1. BCP Entnehmen Sie die erforderlichen Daten aus der vorhandenen Tabelle Tbl2 (ca. 100 Millionen Datensätze, es kann ca. 30 Minuten dauern).
    • Nehmen wir an, ich habe die Aktivität am 1. Februar 2008 um 22:00 Uhr begonnen. Sie wurde am 1. Februar 2008 um 22:30 Uhr beendet. Wenn die Aktivität abgeschlossen ist, erhält die Tabelle Tbl2 neue Datensätze, die Delta werden
  2. Erstellen Sie eine neue Tabelle in der Datenbank mit Name Tbl3
  3. BCP in den exportierten Daten in die neu erstellte Tabelle Tbl3 (ca. 100 Millionen Datensätze, es kann ca. 30 Minuten dauern)
  4. Stoppen Sie den Replikationsjob
  5. Verwenden Sie nach Abschluss des BCP-Eingangs das tsql-Skript, um die neuen Delta-Daten einzufügen.

  6. Die Herausforderung besteht darin, wie mit der Delta-Anweisung "update" umzugehen ist.

  7. Starten Sie die Replikation

Zusätzliche Frage:

Wie gehe ich am besten mit dem Szenario um?

Dharmedra Keshari
quelle

Antworten:

26

Da Sie 90% der Zeilen löschen, empfiehlt es sich, die Zeilen, die Sie benötigen, in eine neue Tabelle mit derselben Struktur zu kopieren, dann ALTER TABLE ... SWITCHdie vorhandene Tabelle durch die neue Tabelle zu ersetzen und die alte Tabelle einfach zu löschen . Informationen zur Syntax finden Sie auf dieser Microsoft Docs-Seite .

Ein einfacher Prüfstand ohne Replikation, der das allgemeine Prinzip zeigt:

Zuerst erstellen wir eine Datenbank für unseren Test:

USE master;
IF (SELECT 1 FROM sys.databases d WHERE d.name = 'SwitchTest') IS NOT NULL
BEGIN
    ALTER DATABASE SwitchTest SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE SwitchTest;
END
CREATE DATABASE SwitchTest;
ALTER DATABASE SwitchTest SET RECOVERY FULL;
BACKUP DATABASE SwitchTest TO DISK = 'NUL:';
GO

Hier erstellen wir einige Tabellen mit einem Auslöser zum Verschieben von Zeilen von Tabelle "A" nach "B", um Ihre Konfiguration zu approximieren.

USE SwitchTest;
GO
CREATE TABLE dbo.A
(
    i int NOT NULL 
        CONSTRAINT PK_A
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , d varchar(300) NOT NULL
    , rowdate datetime NOT NULL
) ON [PRIMARY]
WITH (DATA_COMPRESSION = PAGE);

CREATE TABLE dbo.B
(
    i int NOT NULL 
        CONSTRAINT PK_B
        PRIMARY KEY CLUSTERED
    , d varchar(300) NOT NULL
    , rowdate datetime NOT NULL
) ON [PRIMARY]
WITH (DATA_COMPRESSION = PAGE);

GO
CREATE TRIGGER t_a
ON dbo.A
AFTER INSERT, UPDATE
AS
BEGIN
    SET NOCOUNT ON;
    DELETE
    FROM dbo.B
    FROM dbo.B b
        INNER JOIN deleted d ON b.i = d.i
    INSERT INTO dbo.B (i, d, rowdate)
    SELECT i.i
        , i.d
        , i.rowdate
    FROM inserted i;
END
GO

Hier fügen wir 1.000.000 Zeilen in "A" ein und aufgrund des Triggers werden diese Zeilen auch in "B" eingefügt.

;WITH src AS (
    SELECT i.n
    FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9))i(n)
)
INSERT INTO dbo.A (d, rowdate)
SELECT d = CRYPT_GEN_RANDOM(300), DATEADD(SECOND, s6.n + (s5.n * 100000) + (s4.n * 10000) + (s3.n * 1000) + (s2.n * 100) + (s1.n * 10), '2017-01-01T00:00:00.000')
FROM src s1
    CROSS JOIN src s2
    CROSS JOIN src s3
    CROSS JOIN src s4
    CROSS JOIN src s5
    CROSS JOIN src s6;

Löschen Sie das Transaktionsprotokoll, um zu vermeiden, dass der Raum ausgeht. Führen Sie dies in der Produktion NICHT aus, da die Transaktionsprotokolldaten an das Gerät "NUL" gesendet werden.

BACKUP LOG SwitchTest TO DISK = 'NUL:';
GO

Mit diesem Code wird eine Transaktion erstellt, um sicherzustellen, dass während der Migration von Zeilen in keine der betroffenen Tabellen geschrieben werden kann:

BEGIN TRANSACTION
EXEC sys.sp_getapplock @Resource = N'TableSwitcher', @LockMode = 'Exclusive', @LockOwner = 'Transaction', @LockTimeout = '1000', @DbPrincipal = N'dbo';
BEGIN TRY
    -- create a table to hold the rows we want to keep
    CREATE TABLE dbo.C
    (
        i int NOT NULL 
            CONSTRAINT PK_C
            PRIMARY KEY CLUSTERED
        , d varchar(300) NOT NULL
        , rowdate datetime NOT NULL
    ) ON [PRIMARY]
    WITH (DATA_COMPRESSION = PAGE);

    --copy the rows we want to keep into "C"
    INSERT INTO dbo.C (i, d, rowdate)
    SELECT b.i
        , b.d
        , b.rowdate
    FROM dbo.B
    WHERE b.rowdate >= '2017-01-11T10:00:00';

    --truncate the entire "B" table
    TRUNCATE TABLE dbo.B;

    --"switch" table "C" into "B"
    ALTER TABLE dbo.C SWITCH TO dbo.B;

    --drop table "C", since we no longer need it
    DROP TABLE dbo.C;

    --shows the count of rows in "B" which were retained.
    SELECT COUNT(1)
    FROM dbo.B
    WHERE b.rowdate >= '2017-01-11T10:00:00';

   --look for rows in "B" that should no longer exist.
    SELECT COUNT(1)
    FROM dbo.B
    WHERE b.rowdate < '2017-01-11T10:00:00';

    --release the applock and commit the transaction
    EXEC sys.sp_releaseapplock @Resource = N'TableSwitcher', @LockOwner = 'Transaction', @DbPrincipal = N'dbo';
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    DECLARE @message nvarchar(1000) = ERROR_MESSAGE();
    DECLARE @severity int = ERROR_SEVERITY();
    DECLARE @state int = ERROR_STATE();
    RAISERROR (@message, @severity, @state);
    EXEC sys.sp_releaseapplock @Resource = N'TableSwitcher', @LockOwner = 'Transaction', @DbPrincipal = N'dbo';
    ROLLBACK TRANSACTION;
END CATCH
GO

Das sp_getapplockund sp_releaseapplockverhindern, dass mehrere Instanzen dieses Codes gleichzeitig ausgeführt werden. Dies ist hilfreich, wenn Sie die Wiederverwendung dieses Codes über eine GUI aktivieren.

(Beachten Sie, dass App-Sperren nur wirksam sind, wenn jeder Prozess, der auf die Ressource zugreift, explizit dieselbe manuelle Logik zum Sperren von Ressourcen implementiert. Es gibt keine Magie, die die Tabelle auf die gleiche Weise "sperrt", wie SQL Server während eines Vorgangs automatisch Zeilen, Seiten usw. sperrt Einfügen / Aktualisieren.)

Jetzt testen wir das Einfügen von Zeilen in "A", um sicherzustellen, dass sie vom Trigger in "B" eingefügt werden.

INSERT INTO dbo.A (d, rowdate)
VALUES ('testRow', GETDATE());

SELECT *
FROM dbo.B
WHERE B.d = 'testRow'
+ --------- + --------- + ------------------------- +
| ich | d | Zeilendatum |
+ --------- + --------- + ------------------------- +
| 1000001 | testRow | 2018-04-13 03: 49: 53.343 |
+ --------- + --------- + ------------------------- +
Max Vernon
quelle