使用无序 table 优化删除 SQL 查询

Optimize delete SQL query with unordered table

我正在尝试从具有 80,000,000 行的巨大 table 中批量删除旧数据,将删除大约 50,000,000 行。这将以 50k 为单位进行,以避免数据库日志溢出。 table 的行也没有按时间顺序排序。我想出了以下脚本:

BEGIN
DECLARE  @START_TIME DATETIME,
         @END_TIME  DATETIME,
         @DELETE_COUNT NUMERIC(10,0),
         @TOTAL_COUNT NUMERIC(10,0),
         @TO_DATE DATETIME,
         @FROM_DATE DATETIME,
         @TABLE_SIZE INT
     
SELECT @START_TIME = GETDATE()
PRINT 'Delete script Execution START TIME = %1!', @START_TIME

SELECT @TABLE_SIZE = COUNT(*) FROM HUGE_TABLE
PRINT 'Number of rows in HUGE_TABLE = %1!', @TABLE_SIZE

SELECT @DELETE_COUNT = 1,
       @TOTAL_COUNT  = 0,
       @TO_DATE = DATEADD(yy, -2, GETDATE())
       
CREATE TABLE #TMP_BATCH_FOR_DEL (REQUEST_DT DATETIME)

WHILE(@DELETE_COUNT > 0)
BEGIN

    DELETE FROM #TMP_BATCH_FOR_DEL
    
    INSERT INTO #TMP_BATCH_FOR_DEL (REQUEST_DT)
    SELECT TOP 50000 REQUEST_DT
        FROM HUGE_TABLE 
        WHERE REQUEST_DT < @TO_DATE
        ORDER BY REQUEST_DT DESC
    
    SELECT @FROM_DATE = MIN(REQUEST_DT), @TO_DATE = MAX(REQUEST_DT)
    FROM #TMP_BATCH_FOR_DEL

    PRINT 'Deleting data from %1! to %2!', @FROM_DATE, @TO_DATE

    DELETE FROM HUGE_TABLE
    WHERE REQUEST_DT BETWEEN @FROM_DATE AND @TO_DATE
    
    SELECT @DELETE_COUNT = @@ROWCOUNT
    
    SELECT @TOTAL_COUNT = @TOTAL_COUNT + @DELETE_COUNT
    
    SELECT @TO_DATE = @FROM_DATE
    
    COMMIT
    CHECKPOINT

END 

SELECT @END_TIME = GETDATE()
PRINT 'Delete script Execution END TIME = %1!', @END_TIME
PRINT 'Total Rows deleted = %1!', @TOTAL_COUNT
DROP TABLE #TMP_BATCH_FOR_DEL
END
GO

我做了一个练习 运行,发现上面每小时删除大约 2,250,000 行。因此,删除我的数据需要 24 小时以上的连续 运行时间。

我知道循环中那个该死的 ORDER BY 子句会减慢速度,但是将有序的 table 存储在另一个临时 table 中会占用太多内存。但是,我想不出更好的方法来做到这一点。 想法?

也许您可以通过将要保留的 30.000.000 条记录插入另一个 Table 来优化您的查询,这将是您的新“巨大 Table”。并将整个旧的“巨大 Table” 全部放在一起。

此致

LK

这可能不是查询本身。您的代码每秒 删除大约 600 多条记录。那段时间发生了很多事情——记录、锁定等等。

一种更快的方法是将您想要的数据加载到新的 table,截断旧的 table,然后重新加载:

select *
into temp_huge_table 
from huge_table
where request_dt > ?;  -- whatever the cutoff is

然后 -- 在验证结果之后 -- 截断巨大的 table 并重新加载数据:

truncate table huge_table;

insert into huge_table
    select *
    from temp_huge_table;

如果有标识列,您需要禁用它以允许标识插入。如果存在在 table 中设置值的触发器,您可能必须采取其他预防措施。或者如果在 table.

中存在对行的外键引用

我不建议直接这样做。在截断 table 之后,您可能应该按 table 按日期进行分区——按日、周、月等。

然后,将来您可以简单地删除分区而不是删除行。删除分区要快得多。

请注意,将几千万行加载到一个空的 table 中比删除它们要快得多,但仍然需要时间(您可以在您的系统上测试多少时间)。这就需要闹市了table。但是,希望您有一个可能的维护期。

而且,停机时间可以通过分区 table 来证明,这样你以后就不会遇到这个问题了。