Jaki jest najszybszy sposób na wyczyszczenie danych?

18

Scenariusz:

Mamy dwie tabele Tbl1i Tbl2na serwerze subskrybenta. Tbl1Jest replikowane z Wydawcy Server Ai ma dwa spusty - wstaw i aktualizacji. Wyzwalacze to wstawianie i aktualizowanie danych Tbl2.

Teraz musimy wyczyścić (około 900 milionów rekordów), z Tbl2których łącznie ponad 1000 milionów rekordów. Poniżej przedstawiono rozkład danych z jednego miesiąca do jednej minuty.

  • Jeden miesiąc - 14986826 wierszy
  • Jeden dzień - 483446 rzędów
  • Jedna godzina - 20143 rzędy
  • Jedna minuta - 335 rzędów

Czego szukam;

Najszybszy sposób na usunięcie tych danych bez problemów z produkcją, spójności danych i prawdopodobnie bez przestojów. Więc myślę, aby wykonać poniższe kroki, ale utknąłem :(

Kroki:

  1. BCP Wyjmij wymagane dane z istniejącej tabeli Tbl2 (około 100 milionów rekordów, może to potrwać około 30 minut).
    • Załóżmy, że zacząłem wykonywać tę czynność w dniu 1Fab2018 22:00, a zakończyłem w dniu 1Fab2018 22:30. Do czasu zakończenia działania tabela Tbl2 otrzyma nowe rekordy, które staną się delta
  2. Utwórz nową tabelę w bazie danych o nazwie Tbl3
  3. BCP w wyeksportowanych danych do nowo utworzonej tabeli Tbl3 (około 100 milionów rekordów, może to potrwać około 30 minut)
  4. Zatrzymaj zadanie replikacji
  5. Po zakończeniu BCP-in użyj skryptu tsql, aby wstawić nowe dane delta.

  6. Wyzwanie polega na tym - jak radzić sobie z oświadczeniem „aktualizacja” delta?

  7. Rozpocznij replikację

Dodatkowe pytanie:

Jaki jest najlepszy sposób radzenia sobie ze scenariuszem?

Dharmedra Keshari
źródło

Odpowiedzi:

26

Ponieważ usuwasz 90% wierszy, zalecam skopiowanie wierszy, które musisz zachować, do nowej tabeli o tej samej strukturze, a następnie użyj, ALTER TABLE ... SWITCHaby zastąpić istniejącą tabelę nową tabelą, a następnie po prostu upuść starą tabelę. Zobacz tę stronę Dokumentów Microsoft, aby uzyskać składnię.

Proste stanowisko testowe, bez replikacji, które pokazuje ogólną zasadę:

Najpierw stworzymy bazę danych dla naszego testu:

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

Tutaj tworzymy kilka tabel z wyzwalaczem do przenoszenia wierszy z tabeli „A” do „B”, przybliżając konfigurację.

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

W tym miejscu wstawiamy 1 000 000 wierszy do „A”, a ze względu na wyzwalacz te wiersze również zostaną wstawione do „B”.

;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;

Wyczyść dziennik transakcji, aby uniknąć braku miejsca. NIE URUCHAMIAJ tego podczas produkcji, ponieważ wysyła dane dziennika transakcji do urządzenia „NUL”.

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

Ten kod tworzy transakcję, aby zapewnić, że żadna z tabel, których dotyczy problem, nie może zostać zapisana podczas migracji wierszy:

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

sp_getapplockI sp_releaseapplockzapobiec wielu instancji tego kodu działa w tym samym czasie. Byłoby to pomocne, jeśli umożliwisz ponowne użycie tego kodu za pomocą GUI.

(Pamiętaj, że blokady aplikacji są skuteczne tylko wtedy, gdy wszystkie proces uzyskujący dostęp do zasobu implementuje jawnie tę samą logikę ręcznego blokowania zasobów - nie ma magii, która „blokuje” tabelę w taki sam sposób, w jaki SQL Server automatycznie blokuje wiersze, strony itp. Podczas operacja wstawiania / aktualizacji).

Teraz testujemy proces wstawiania wierszy do „A”, aby upewnić się, że są wstawiane do „B” przez wyzwalacz.

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

SELECT *
FROM dbo.B
WHERE B.d = 'testRow'
+ --------- + --------- + ------------------------- +
| i | d | data wiersza |
+ --------- + --------- + ------------------------- +
| 1000001 | testRow | 2018-04-13 03: 49: 53.343 |
+ --------- + --------- + ------------------------- +
Max Vernon
źródło