Qual é a maneira mais rápida de limpar dados?


18

Cenário:

Temos duas tabelas Tbl1e Tbl2no servidor de assinante. O Tbl1está sendo replicado do Publisher Server Ae possui dois gatilhos - inserção e atualização. Os gatilhos estão inserindo e atualizando os dados Tbl2.

Agora, temos que limpar (aproximadamente 900 milhões de registros) dos Tbl2quais possui mais de 1000 milhões de registros. Abaixo está a distribuição de dados de um mês a um minuto.

  • Um mês - 14986826 linhas
  • Um dia - 483446 linhas
  • Uma hora - 20143 linhas
  • Um minuto - 335 linhas

O que estou procurando;

A maneira mais rápida de limpar esses dados sem nenhum problema de produção, consistência dos dados e possivelmente sem tempo de inatividade. Então, eu estou pensando em seguir as etapas abaixo, mas preso :(

Passos:

  1. BCP Fora dos dados necessários da tabela existente Tbl2 (cerca de 100 milhões de registros, pode levar aproximadamente 30 minutos).
    • Vamos supor que comecei a realizar a atividade em 1Fab2018 22:00, e terminou em 1Fab2018 22:30. Quando a atividade estiver concluída, a tabela Tbl2 receberá novos registros que se tornam delta
  2. Crie uma nova tabela no banco de dados com o nome Tbl3
  3. BCP nos dados exportados para a tabela Tbl3 recém-criada (cerca de 100 milhões de registros, pode levar aproximadamente 30 minutos)
  4. Pare o trabalho de replicação
  5. Depois que o BCP-in for concluído, use o script tsql para inserir os novos dados delta.

  6. O desafio é - como lidar com a declaração delta "update"?

  7. Iniciar a replicação

Pergunta adicional:

Qual é a melhor maneira de lidar com o cenário?

Respostas:


26

Como você está excluindo 90% das linhas, recomendo copiar as linhas necessárias para manter em uma nova tabela com a mesma estrutura, usar ALTER TABLE ... SWITCHpara substituir a tabela existente pela nova e simplesmente largar a tabela antiga. Consulte esta página do Microsoft Docs para obter a sintaxe.

Um banco de ensaio simples, sem replicação, que mostra o princípio geral:

Primeiro, criaremos um banco de dados para o nosso teste:

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

Aqui, criamos algumas tabelas, com um gatilho para mover linhas da tabela "A" para "B", aproximando sua configuração.

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

Aqui, inserimos 1.000.000 de linhas em "A" e, devido ao gatilho, essas linhas também serão inseridas em "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;

Limpe o log de transações, para evitar ficar sem espaço. NÃO EXECUTE isso em produção, pois ele envia os dados do log de transações para o dispositivo "NUL".

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

Esse código cria uma transação para garantir que nenhuma das tabelas afetadas possa ser gravada enquanto estamos migrando linhas:

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

O sp_getapplocke sp_releaseapplockimpede que várias instâncias desse código sejam executadas ao mesmo tempo. Isso seria útil se você habilitar esse código para ser reutilizado por meio de uma GUI.

(Observe que os bloqueios de aplicativos são eficazes apenas se todos os processos que acessam o recurso implementam explicitamente a mesma lógica manual de bloqueio de recursos - não há mágica que "bloqueie" a tabela da mesma maneira que o SQL Server bloqueia automaticamente linhas, páginas etc. durante uma operação de inserção / atualização.)

Agora, testamos o processo de inserção de linhas em "A", para garantir que elas sejam inseridas em "B" pelo gatilho.

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

SELECT *
FROM dbo.B
WHERE B.d = 'testRow'
+ --------- + --------- + ------------------------- +
| eu d data da linha |
+ --------- + --------- + ------------------------- +
| 1000001 testRow | 13-04-2013 03: 49: 53.343 |
+ --------- + --------- + ------------------------- +
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.