Qual è il modo più rapido per eliminare i dati?


18

Scenario:

Abbiamo due tabelle Tbl1e Tbl2sul server degli abbonati. Il Tbl1viene replicata da Publisher Server Ae ha due trigger - inserimento e aggiornamento. I trigger stanno inserendo e aggiornando i dati in Tbl2.

Ora, dobbiamo eliminare (circa 900 milioni di record) da Tbl2cui ha registrato un totale di oltre 1000 milioni di record. Di seguito è riportata la distribuzione dei dati per un mese o un minuto.

  • Un mese - 14986826 righe
  • Un giorno - 483446 file
  • Un'ora - 20143 righe
  • Un minuto - 335 file

Quello che sto cercando;

Il modo più veloce per eliminare tali dati senza problemi di produzione, coerenza dei dati e possibilmente senza tempi di inattività. Quindi, sto pensando di seguire i passaggi seguenti ma bloccato :(

passi:

  1. BCP Estrarre i dati richiesti dalla tabella esistente Tbl2 (circa 100 milioni di record, potrebbero essere necessari circa 30 minuti).
    • Supponiamo che io abbia iniziato a svolgere l'attività su 1Fab2018 10:00 PM, è terminato a 1Fab2018 10:30 PM. Quando l'attività sarà completata, la tabella Tbl2 otterrà nuovi record che diventano delta
  2. Creare una nuova tabella nel database con il nome Tbl3
  3. BCP nei dati esportati nella tabella appena creata Tbl3 (circa 100 milioni di record, potrebbero essere necessari circa 30 minuti)
  4. Interrompere il processo di replica
  5. Una volta completato il BCP-in, utilizzare lo script tsql per inserire i nuovi dati delta.

  6. La sfida è: come gestire la dichiarazione di "aggiornamento" delta?

  7. Inizia la replica

Domanda aggiuntiva:

Qual è il modo migliore per affrontare lo scenario?

Risposte:


26

Poiché stai eliminando il 90% delle righe, ti consiglio di copiare le righe che devi conservare in una nuova tabella con la stessa struttura, quindi utilizzare ALTER TABLE ... SWITCHper sostituire la tabella esistente con la nuova tabella, quindi semplicemente eliminare la vecchia tabella. Vedi questa pagina di Microsoft Documenti per la sintassi.

Un semplice banco di prova, senza replica che mostra il principio generale:

Innanzitutto, creeremo un database per il nostro 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

Qui, creiamo un paio di tabelle, con un trigger per spostare le righe dalla tabella "A" a "B", approssimando la configurazione.

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

Qui inseriamo 1.000.000 di righe in "A" e, a causa del trigger, anche quelle righe verranno inserite in "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;

Cancella il registro delle transazioni, per evitare di rimanere senza spazio. NON ESEGUIRE questo in produzione poiché invia i dati del registro delle transazioni al dispositivo "NUL".

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

Questo codice crea una transazione per garantire che nessuna delle tabelle interessate possa essere scritta durante la migrazione di righe:

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

L' sp_getapplocke sp_releaseapplockprevenire più istanze di questo codice in esecuzione allo stesso tempo. Ciò sarebbe utile se si abilita il riutilizzo di questo codice tramite una GUI.

(Si noti che i blocchi delle app sono efficaci solo se ogni processo che accede alla risorsa implementa esplicitamente la stessa logica di blocco manuale delle risorse - non c'è magia che "blocchi" la tabella nello stesso modo in cui SQL Server blocca automaticamente righe, pagine, ecc. Durante un operazione di inserimento / aggiornamento.)

Ora, testiamo il processo di inserimento delle righe in "A", per assicurarci che siano inserite in "B" dal trigger.

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

SELECT *
FROM dbo.B
WHERE B.d = 'testRow'
+ --------- + --------- + ------------------------- +
| io | d | riga |
+ --------- + --------- + ------------------------- +
| 1000001 | testRow | 13/04/2018 03: 49: 53.343 |
+ --------- + --------- + ------------------------- +
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.