优化表中5000万条记录的删除操作

时间:2013-12-03 22:04:38

标签: sql sql-server sql-server-2008 sql-server-2008-r2

我需要删除大约5000万条记录(而不是整张表) 我搜索并找到了一些方法来做到这一点

此查询执行我想要的操作,我可以通过将子查询的结果存储在#TempTable

中来优化它

我还能做什么?

目前为止最快的查询:

CREATE TABLE #UserIDs ( UserId UNIQUEIDENTIFIER  NOT NULL  );
CREATE CLUSTERED INDEX myIndex ON #UserIDs (UserId)

INSERT INTO #UserIDs 
SELECT UserId FROM TableX WHERE UserID IS NOT NULL;

INSERT INTO #UserIDs 
SELECT UserID FROM TableY WHERE CreatorID IS NOT NULL;

    DELETE TOP (10000)
    FROM Users
    WHERE 
        Email IS NULL
        AND
        (
            (NOT EXISTS ( SELECT 1 FROM #UserIDs WHERE #UserIDs.UserId = Users.UserId ) )
        )

DROP TABLE #UserIDs

Execution Plan

7 个答案:

答案 0 :(得分:6)

如果要保留的记录数量与您需要删除的记录数量相比较小,我会将它们复制到临时表中,然后TRUNCATE原始表格并从临时表中插入保留的记录。表截断的速度非常快,因此可以节省大量时间。但是TRUNCATE TABLE并不总是有效,但它可以为您提供解决方案。

答案 1 :(得分:2)

首先运行第1部分:

SELECT u.UserId
INTO #DeleteThis
FROM Users u
LEFT JOIN tableX x
    ON u.UserId = x.UserId
LEFT JOIN tableY y
    ON u.UserId = y.UserId
WHERE SomeCondition IS NULL --SomeCondition is indexed
    AND x.UserId IS NULL
    AND y.UserId IS NULL

然后运行第2部分:

DELETE TOP (1000)
FROM Users u
JOIN #DeleteThis d    
    ON u.UserId = d.UserId

GO 50000

答案 2 :(得分:2)

无法看到执行平面,很难说,但是......

我倾向于创建一个包含“守护者”列表的工作表:任何不在该列表中的人都会被删除:编译该列表的一次性前期成本。

假设userId是用户表的主键并被编入索引,则工作表仅包含userId,其定义为primary key clustered。这意味着delete语句只需要探测工作表以确定需要从用户表中删除哪些行。然后这是一个简单的事情:

--
-- create/populate our work table containing the list of keepers
-- anybody not in this list gets deleted
--
create table #keepers
(
  userId int not null primary key clustered ,
)

insert #keepers (userId)
select t.userId
from dbo.Users t
join dbo.TableX x on x.userId = t.userId
join dbo.TableY y on y.userId = t.userID
where t.someCondition is not null

--
-- iterate, deleting a batch at a time until nothing has been deleted
--
declare @batch_size int     = 10000 -- or whatever you decide is good
declare @finished   char(1) = 'N'
while ( @finished = 'N' )
begin

  delete top @batch_size dbo.Users
  from dbo.Users t
  where not exists ( select *
                     from #keepers k
                     where k.userID = t.userId
                   )

  set @finished = case @@rowcount when 0 then 'Y' else 'N' end
end

如果您的应用程序的性质使得用户可以在此过程中从守护者列表转换到失败者列表,您可能想尝试类似以下内容。具有exists的相关子查询通常比未校正的not in更好(假设一些合理的索引方案)。所以,这样的事情可能会表现得更好:

declare @batch_size int     = 10000 -- or whatever you decide is good
declare @finished   char(1) = 'N'
while ( @finished = 'N' )
begin

  delete top @batch_size dbo.Users
  from dbo.Users t
  where u.someCondition is null
    and (    not exists ( select * from dbo.tableX x on x.userID = u.userId )
          OR not exists ( select * from dbo.tableY y on y.userID = u.userId )
        )

  set @finished = case @@rowcount when 0 then 'Y' else 'N' end
end

答案 3 :(得分:1)

这个怎么样:

DELETE FROM Users
WHERE 
SomeCondition IS NULL --SomeCondition is indexed
AND
(
    (NOT EXISTS ( SELECT 1 FROM TableX WHERE TableX.UserId = Users.UserId ) )
    OR
    (NOT EXISTS ( SELECT 1 FROM TableY WHERE TableY.UserId = Users.UserId ) )
)

运行它并告诉我你的结果。

答案 4 :(得分:1)

我正在考虑以下情况:

1 /创建一个名为“UsersClone”的新表,作为“Users”表的副本

2 /平安地删除克隆中的记录

3 /添加克隆新添加的用户(如果有的话)

4 /将“Users”重命名为“UsersToDelete”(请参阅​​sp_rename)

5 /将克隆重命名为“Users”

6 /删除表“UsersToDelete”

你怎么看?

答案 5 :(得分:1)

如果您要保留的行数很少,我建议您使用copy-to-new-table方法。如果数字很高,停机时间太长而且你不能这样做。

目前您遇到了问题,因为删除脚本的每次执行都需要比上一次更长的时间。必须扫描表中较大和较大的部分以查找要删除的行。

老实说,您的表应该是ID列上的聚簇索引。但鉴于它不是,我建议您将所有用户ID实现删除到临时表中。在该临时表的UserID列上放置聚簇索引。所有这些都是在线的。

接下来,您可以批量删除堆:

DELETE TOP (1000) u
FROM Users u
JOIN #userIDs ids on u.ID = ids.UserID --uses indexes on both tables
WHERE ids.UserID > @lastDeletedUserID --start where last batch stopped
ORDER BY ids.UserID --delete in order of the table

每次迭代需要相同的时间。您只需跟踪已删除的最后一个ID(@lastDeletedUserID)。

答案 6 :(得分:1)

作为Truncates的替代方案,您是否尝试将子选项移到单独的部分?您是一遍又一遍地查询该组数据,将它转储到临时表并使用它可能会更快?

Declare @temp Table (ID int)

Insert into @temp
select UserId FROM TableX
union
select UserId FROM TableY;

DoItAgain:
    DELETE TOP (1000)
    FROM Users
    WHERE 
        SomeCondition IS NULL --SomeCondition is indexed
        AND UserID not in (select UserId From @temp)

IF @@ROWCOUNT > 0
GOTO DoItAgain