15

我遇到了 SQL 性能问题。由于突然的原因,以下查询非常慢:

我有两个列表,其中包含某个表的 ID。如果 ID 已存在于第二个列表中,我需要从第一个列表中删除所有记录:

DECLARE @IdList1 TABLE(Id INT)
DECLARE @IdList2 TABLE(Id INT)

-- Approach 1
DELETE list1
FROM @IdList1 list1
INNER JOIN @IdList2 list2 ON list1.Id = list2.Id

-- Approach 2
DELETE FROM @IdList1
WHERE Id IN (SELECT Id FROM @IdList2)

这两个列表可能包含超过 10.000 条记录。在这种情况下,两个查询的执行时间都超过 20 秒。

执行计划也显示了一些我不明白的地方。也许这解释了为什么它这么慢: 两个查询的查询计划

我用 10.000 个连续整数填充了两个列表,因此两个列表都包含值 1-10.000 作为起点。

如您所见,@IdList2 的两个查询显示实际行数为 50.005.000 !!。@IdList1 是正确的(实际行数为 10.000)

我知道还有其他解决方案如何解决这个问题。就像填写第三个列表而不是从第一个列表中删除一样。但我的问题是:

为什么这些删除查询这么慢,为什么我会看到这些奇怪的查询计划?

4

7 回答 7

16

将主键添加到表变量并观看它们尖叫

DECLARE @IdList1 TABLE(Id INT primary Key not null)
DECLARE @IdList2 TABLE(Id INT primary Key not null)

因为这些表变量没有索引,所以任何连接或子查询都必须检查 10,000 乘以 10,000 = 100,000,000 对值的顺序。

于 2013-05-23T13:02:46.593 回答
12

SQL Server 在表变量为空时编译计划,而在添加行时不重新编译它。尝试

DELETE FROM @IdList1
WHERE Id IN (SELECT Id FROM @IdList2)
OPTION (RECOMPILE)

这将考虑到表变量中包含的实际行数并摆脱嵌套循环计划

当然,Id通过约束创建索引也可能对使用表变量的其他查询有益。

于 2013-05-23T13:02:54.963 回答
2

表变量中的表可以有主键,因此如果您的数据支持这些Ids 的唯一性,您可以通过执行

DECLARE @IdList1 TABLE(Id INT PRIMARY KEY)
DECLARE @IdList2 TABLE(Id INT PRIMARY KEY)
于 2013-05-23T13:03:23.607 回答
2

可能的解决方案:

1)尝试创建索引

1.1)如果 List{1|2}.Id 列具有唯一值,那么您可以使用这样的 PK 约束定义唯一聚集索引:

DECLARE @IdList1 TABLE(Id INT PRIMARY KEY);
DECLARE @IdList2 TABLE(Id INT PRIMARY KEY);

1.2) 如果 List{1|2}.Id 列可能有重复值,那么您可以使用 PK 约束使用IDENTITY如下虚拟列定义唯一聚集索引:

DECLARE @IdList1 TABLE(Id INT, DummyID INT IDENTITY, PRIMARY KEY (ID, DummyID) );
DECLARE @IdList2 TABLE(Id INT, DummyID INT IDENTITY, PRIMARY KEY (ID, DummyID) );

2)尝试添加HASH JOIN这样的查询提示:

DELETE list1
FROM @IdList1 list1
INNER JOIN @IdList2 list2 ON list1.Id = list2.Id
OPTION (HASH JOIN);
于 2013-05-23T13:10:11.380 回答
1

您正在使用Table Variables,要么向表中添加一个主键,要么将它们更改为Temporary Tables并添加一个INDEX. 这将带来更多的性能。根据经验,如果表很小,请使用TABLE Variables,但是如果表正在扩展并且包含大量数据,则可以使用临时表。

于 2013-05-23T13:03:57.723 回答
0

我很想尝试

DECLARE @IdList3 TABLE(Id INT);

INSERT @IdList3
SELECT Id FROM @IDList1 ORDER BY Id
EXCEPT
SELECT Id FROM @IDList2 ORDER BY Id

无需删除。

于 2013-05-23T13:11:53.797 回答
-1

试试这个替代语法:

DELETE deleteAlias
FROM @IdList1 deleteAlias
WHERE EXISTS (
        SELECT NULL
        FROM @IdList2 innerList2Alias
        WHERE innerList2Alias.id=deleteAlias.id
    )

编辑.....................

尝试使用带有索引的#temp 表。

这是一个通用示例,其中“DepartmentKey”是 PK 和 FK。

IF OBJECT_ID('tempdb..#Department') IS NOT NULL
begin
        drop table #Department
end


CREATE TABLE #Department 
( 
    DepartmentKey int , 
    DepartmentName  varchar(12)
)



CREATE INDEX IX_TEMPTABLE_Department_DepartmentKey ON #Department (DepartmentKey)




IF OBJECT_ID('tempdb..#Employee') IS NOT NULL
begin
        drop table #Employee
end


CREATE TABLE #Employee 
( 
    EmployeeKey int , 
    DepartmentKey int ,
    SSN  varchar(11)
)



CREATE INDEX IX_TEMPTABLE_Employee_DepartmentKey ON #Employee (DepartmentKey)


Delete deleteAlias 
from #Department deleteAlias
where exists ( select null from #Employee innerE where innerE.DepartmentKey = deleteAlias.DepartmentKey )





IF OBJECT_ID('tempdb..#Employee') IS NOT NULL
begin
        drop table #Employee
end

IF OBJECT_ID('tempdb..#Department') IS NOT NULL
begin
        drop table #Department
end
于 2013-05-23T13:07:17.527 回答