使用无序 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 来证明,这样你以后就不会遇到这个问题了。
我正在尝试从具有 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 来证明,这样你以后就不会遇到这个问题了。