Який найшвидший спосіб очищення даних?


18

Сценарій:

У нас є дві таблиці Tbl1& Tbl2на Абонентському сервері. Tbl1Тиражується від видавця Server Aі має два тригера - вставки і оновлення. Тригери вставляють і оновлюють дані в Tbl2.

Тепер нам належить очистити (приблизно 900 мільйонів записів), з Tbl2яких загальна кількість 1000+ мільйонів записів. Нижче наведено розподіл даних за один місяць до однієї хвилини.

  • Один місяць - 14986826 рядів
  • Один день - 483446 рядів
  • Одна година - 20143 ряд
  • Одна хвилина - 335 рядів

Що я шукаю;

Найшвидший спосіб очищення цих даних без жодних виробничих проблем, узгодженості даних і, можливо, без простоїв. Отже, я думаю, щоб виконати наступні кроки, але затримався :(

Кроки:

  1. BCP Вийміть потрібні дані з існуючої таблиці Tbl2 (близько 100 мільйонів записів, це може зайняти приблизно 30 хвилин).
    • Припустимо, я почав займатися 1Fab2018 22:00, він закінчився 1Fab2018 22:30. До того часу, коли активність буде завершена, таблиця Tbl2 отримає нові записи, які стають дельтами
  2. Створіть нову таблицю в базі даних з назвою Tbl3
  3. BCP експортованих даних у новостворену таблицю Tbl3 (близько 100 мільйонів записів, це може зайняти приблизно 30 хвилин)
  4. Зупиніть завдання реплікації
  5. Після завершення BCP-in використовуйте скрипт tsql, щоб вставити нові дані дельти.

  6. Виклик полягає в тому, як боротися з дельтою «оновлення» заяви?

  7. Почніть реплікацію

Додаткове запитання:

Який найкращий спосіб впоратися зі сценарієм?

Відповіді:


26

Оскільки ви видаляєте 90% рядків, я рекомендую скопіювати рядки, які потрібно зберегти, у нову таблицю з тією ж структурою, а потім використати ALTER TABLE ... SWITCHдля заміни існуючої таблиці на нову таблицю, а потім просто скинути стару таблицю. Дивіться на цій сторінці Документи Microsoft щодо синтаксису.

Простий тестовий шар, без тиражування, який показує загальний принцип:

Спочатку ми створимо базу даних для нашого тесту:

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

Тут ми створюємо пару таблиць з тригером для переміщення рядків з таблиці "А" в "В", наближаючи до вашої настройки.

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

Тут ми вставляємо 1 000 000 рядків у "A", а через тригер ці рядки також будуть вставлені у "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;

Очистіть журнал транзакцій, щоб уникнути нестачі місця. НЕ ДАЙТЕ це у виробництві, оскільки він надсилає дані журналу транзакцій на пристрій "NUL".

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

Цей код створює транзакцію, щоб гарантувати, що жодна із таблиць, на які вони впливають, не може бути записана, поки ми мігруємо рядки:

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_getapplockІ sp_releaseapplockзапобігти появі декількох екземплярів цього коду працює одночасно. Це буде корисно, якщо ви включите повторне використання цього коду через GUI.

(Зауважте, що блокування програм є ефективним лише у тому випадку, коли кожен процес, що звертається до ресурсу, явно реалізує ту саму логіку блокування ресурсів вручну - не існує магії, яка "блокує" таблицю так само, як SQL Server автоматично блокує рядки, сторінки тощо під час операція вставлення / оновлення.)

Тепер ми перевіряємо процес вставки рядків у "A", щоб переконатися, що вони вставлені в "B" тригером.

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

SELECT *
FROM dbo.B
WHERE B.d = 'testRow'
+ --------- + --------- + ------------------------- +
| я | d | строкові строки |
+ --------- + --------- + ------------------------- +
| 1000001 | testRow | 2018-04-13 03: 49: 53.343 |
+ --------- + --------- + ------------------------- +
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.