Sql 非常慢的删除查询

Sql 非常慢的删除查询,sql,sql-server,performance,sql-execution-plan,table-variable,Sql,Sql Server,Performance,Sql Execution Plan,Table Variable,我在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 -- Appr

我在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)
这两个列表可能包含超过10000条记录。在这种情况下,两个查询的执行时间都超过20秒

执行计划也显示了一些我不理解的东西。也许这就是为什么它如此缓慢的原因:

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

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

我知道还有其他解决办法。就像填写第三份清单而不是从第一份清单中删除一样。但我的问题是:


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

在表变量中添加主键,然后看着它们尖叫

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

因为这些表变量上没有索引,所以任何联接或子查询都必须按照10000乘以10000=100000000对值的顺序进行检查。

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

DELETE FROM @IdList1
WHERE Id IN (SELECT Id FROM @IdList2)
OPTION (RECOMPILE)
这将考虑表变量中包含的实际行数,并消除嵌套循环计划


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

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

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

您正在使用
表变量
,或者向表中添加主键,或者将它们更改为
临时表
,然后添加
索引
。这将带来更高的性能。根据经验,如果表很小,可以使用
表变量
,但是如果表正在扩展且包含大量数据,则可以使用临时表

试试这种替代语法:

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

尝试使用带有索引的临时表

这里是一个通用示例,“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
可能的解决办法:

1) 尝试创建这样的索引

1.1)如果列表{1 | 2}.Id列具有唯一的值,则可以使用如下PK约束定义唯一的聚集索引:

DECLARE @IdList1 TABLE(Id INT PRIMARY KEY);
DECLARE @IdList2 TABLE(Id INT PRIMARY KEY);
DECLARE @IdList1 TABLE(Id INT, DummyID INT IDENTITY, PRIMARY KEY (ID, DummyID) );
DECLARE @IdList2 TABLE(Id INT, DummyID INT IDENTITY, PRIMARY KEY (ID, DummyID) );
DELETE list1
FROM @IdList1 list1
INNER JOIN @IdList2 list2 ON list1.Id = list2.Id
OPTION (HASH JOIN);
1.2)如果列表{1 | 2}.Id列可能有重复的值,则可以使用PK约束,使用伪
标识
列定义唯一的聚集索引,如下所示:

DECLARE @IdList1 TABLE(Id INT PRIMARY KEY);
DECLARE @IdList2 TABLE(Id INT PRIMARY KEY);
DECLARE @IdList1 TABLE(Id INT, DummyID INT IDENTITY, PRIMARY KEY (ID, DummyID) );
DECLARE @IdList2 TABLE(Id INT, DummyID INT IDENTITY, PRIMARY KEY (ID, DummyID) );
DELETE list1
FROM @IdList1 list1
INNER JOIN @IdList2 list2 ON list1.Id = list2.Id
OPTION (HASH JOIN);
2) 尝试添加如下的
HASH JOIN
查询提示:

DECLARE @IdList1 TABLE(Id INT PRIMARY KEY);
DECLARE @IdList2 TABLE(Id INT PRIMARY KEY);
DECLARE @IdList1 TABLE(Id INT, DummyID INT IDENTITY, PRIMARY KEY (ID, DummyID) );
DECLARE @IdList2 TABLE(Id INT, DummyID INT IDENTITY, PRIMARY KEY (ID, DummyID) );
DELETE list1
FROM @IdList1 list1
INNER JOIN @IdList2 list2 ON list1.Id = list2.Id
OPTION (HASH JOIN);
我很想试试

DECLARE @IdList3 TABLE(Id INT);

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

不需要删除。

@IdList1
上有索引会有帮助吗?“任何联接或子查询都必须按10000乘以10000=100000000对值的顺序进行检查。”这仅适用于嵌套循环。散列或合并联接会对每个输入处理一次(尽管合并联接也需要排序)@martin,我已经有一段时间没有读过这些东西了,所以我忘记了规则,但它不是因为没有索引而选择嵌套循环吗?要执行其他循环算法,不需要索引来对值进行排序吗?此外,在没有索引的情况下,它仍然必须检查每一对值——不管它使用什么循环算法来创建它们。-正如您所注意到的,异常是一个合并联接,但它必须对它们进行预排序。@CharlesBretana-不,只要存在相等联接,它就可以使用哈希或合并联接。合并联接将需要对两个输入进行排序(创建索引也是如此),但一旦创建了索引,显然它可能会更有用,因为它将有利于其他查询(因此+1)。您的答案和注释与@MartinSmith一起是一个巨大的改进。谢谢这对我来说是新的。您能澄清一下吗?当遇到Delete语句时,会发生cacheplan初始编译,对吗?不是在声明表变量时?我的意思是,正在编译的计划是为了删除,而不是为了表变量声明。。。如果是这样的话,那么在那一点上,表变量不会被填充吗?另外,如果你不介意的话,你能提供一份推荐信吗?我想仔细阅读一下。@CharlesBretana-这里有一些链接和示例代码,但是,因为cacheplan是为每个语句创建的,而不是为整个批处理或存储过程创建的,它是否在开始执行前为批处理或过程中的每个语句创建缓存计划?@CharlesBretana-它在执行前编译批处理中的所有语句,除非该语句引用不存在的对象并标记为延迟编译。因此,在这种情况下,当表变量为空时,将编译
DELETE
语句。然后(由于
选项(重新编译)
),它在
删除点重新编译,并且可以考虑填充表变量后的实际行数。您的答案和注释以及@CharlesBretana是一个巨大的改进。我决定接受查理的回答,因为我不能接受两个答案;)。谢谢不幸的是,这也很慢。相同的结果和完全相同的查询计划。您是被迫使用@variable tables,还是可以尝试使用#temp tables?如果可以使用#temp tables,请尝试我的回答中的示例。这是现实场景中可能遇到的问题还是,就在这种特殊情况下?@Jodrell-表变量没有基于统计信息的重新编译(并且缺少有用的索引)的根本问题非常普遍。但是如果OP需要删除怎么办,就像他/她说的:
如果第二个列表中已经存在Id,我需要删除第一个列表中的所有记录
@oleksii true,OP表明它是一个有关w的人为例子