清除数据的最快方法是什么?

Dha*_*ari 18 trigger sql-server archive

设想:

我们有两个表Tbl1&Tbl2在订阅服务器上。在Tbl1正在从出版商复制的Server A,它有两个触发器-插入和更新。触发器将数据插入和更新到Tbl2.

现在,我们必须清除(大约 9 亿条记录),Tbl2其中总共有 1000+ 万条记录。下面是一个月到一分钟的数据分布。

  • 一个月 - 14986826 行
  • 一天 - 483446 行
  • 一小时 - 20143 行
  • 一分钟 - 335 行

我在找什么;

在没有任何生产问题、数据一致性和可能没有停机时间的情况下清除该数据的最快方法。所以,我想按照以下步骤操作,但卡住了:(

脚步:

  1. BCP 从现有表 Tbl2 中取出所需的数据(大约 1 亿条记录,可能需要大约 30 分钟)。
    • 假设我在 1Fab2018 晚上 10:00 开始做活动,它在 1Fab2018 晚上 10:30 结束。到活动完成时,表 Tbl2 将获得成为 delta 的新记录
  2. 在数据库中创建一个名为 Tbl3 的新表
  3. 导出数据中的 BCP 到新创建的表 Tbl3(大约 1 亿条记录,可能需要大约 30 分钟)
  4. 停止复制作业
  5. 一旦 BCP-in 完成,使用 tsql 脚本插入新的增量数据。

  6. 挑战是 -如何处理增量“更新”语句?

  7. 开始复制

补充问题:

处理场景的最佳方法是什么?

Han*_*non 26

由于您要删除 90% 的行,我建议您将需要保留的行复制到具有相同结构的新表中,然后使用ALTER TABLE ... SWITCH新表替换现有表,然后简单地删除旧表。有关语法,请参阅此 Microsoft Docs 页面

一个简单的测试台,没有复制,显示了一般原则:

首先,我们将为我们的测试创建一个数据库:

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
Run Code Online (Sandbox Code Playgroud)

在这里,我们创建了几个表,使用触发器将行从表“A”移动到“B”,近似于您的设置。

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
Run Code Online (Sandbox Code Playgroud)

在这里,我们将 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;
Run Code Online (Sandbox Code Playgroud)

清除事务日志,避免空间不足。不要在生产中运行它,因为它会将事务日志数据发送到“NUL”设备。

BACKUP LOG SwitchTest TO DISK = 'NUL:';
GO
Run Code Online (Sandbox Code Playgroud)

此代码创建一个事务以确保在我们迁移行时不会写入任何受影响的表:

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
Run Code Online (Sandbox Code Playgroud)

sp_getapplocksp_releaseapplock防止在同一时间运行此代码的多个实例。如果您允许通过 GUI 重新使用此代码,这将很有帮助。

(请注意,应用程序锁仅在访问资源的每个进程显式实现相同的手动资源锁定逻辑时才有效- 没有像 SQL Server 自动锁定行、页等那样“锁定”表的魔法。插入/更新操作。)

现在,我们测试将行插入到“A”中的过程,以确保它们被触发器插入到“B”中。

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

SELECT *
FROM dbo.B
WHERE B.d = 'testRow'
Run Code Online (Sandbox Code Playgroud)
+---------+---------+--------------------------+
| 我| d | 行日期 |
+---------+---------+--------------------------+
| 1000001 | 测试行 | 2018-04-13 03:49:53.343 |
+---------+---------+--------------------------+