为什么打破这个相关子查询会大大提高性能?

时间:2014-06-03 11:18:11

标签: sql sql-server tsql

我尝试对两个大小不同的表运行此查询 - #temp约为15,000行,成员约为70,000,000,其中约68,000,000个没有ID 307。

SELECT COUNT(*) 
FROM #temp
WHERE CAST(individual_id as varchar) NOT IN (
        SELECT IndividualID
        FROM Member m
        INNER JOIN Person p ON p.PersonID = m.PersonID
        WHERE CompanyID <>  307)

这个查询运行了18个小时,然后我杀了它并尝试了其他的东西,这是:

SELECT IndividualID
INTO #source
FROM Member m
INNER JOIN Person p ON p.PersonID = m.PersonID
WHERE CompanyID <> 307

SELECT COUNT(*)
FROM #temp
WHERE CAST(individual_id AS VARCHAR) NOT IN (
        SELECT IndividualID
        FROM #source)

在给我一个结果之前,这种情况持续了不到一秒钟。

我对此感到非常惊讶。我是一个中间层开发人员而不是SQL专家,而且我对引擎盖下发生的事情的理解有点模糊,但我会假设,因为我第一次尝试的子查询是完全相同的代码,要求与第二次尝试完全相同的数据,这些数据大致相当。

但这显然是错的。我无法查看原始查询的执行计划,以查看SQL Server正在尝试执行的操作。那么有人可以解释为什么将数据拆分到临时表中要快得多吗?

编辑:表架构和索引

#temp表有两列,Individual_ID intSource_Code varchar(50)

MemberPerson更复杂。它们分别有29列和13列,所以我真的不想全部发布它们。 PersonID是一个int,是Person上的PK和成员上的FK。 IndividualID是Person上的一列 - 这在查询中并不清楚。

在提出问题之前,我尝试使用LEFT JOIN代替NOT IN。第二个查询的性能没有明显不同 - 两者都是亚秒级。在第一个查询中,我让它在停止前运行一个小时,假设它没有显着差异。

我还在#source上添加了索引,就像在原始表上一样,因此性能影响应该相同。

2 个答案:

答案 0 :(得分:6)

首先,您的查询有两个真正突出的失误。您正在转换为varchar(),但不包含长度参数。这不应该被允许!默认长度因上下文而异,您需要明确。

其次,您在不同的表中匹配两个键,它们看起来有不同的类型。外键引用应始终具有相同的类型。这会对性能产生很大影响。如果您正在处理具有数百万行的表,那么您需要注意数据结构。

要了解性能差异,您需要了解执行计划。这两个查询具有非常不同的执行计划。我的(有教养的)猜测是第一个版本版本使用嵌套循环连接算法。第二个版本使用更复杂的算法。在您的情况下,这可能是由于SQL Server能够维护表的统计信息。因此,实例化中间结果实际上有助于优化器生成更好的查询计划。

对如何最好地编写这种逻辑的主题进行了大量研究。 Here是Aaron Bertrand关于这个主题的非常好的讨论。

我同意Aaron在这种情况下对not exists的偏好:

SELECT COUNT(*) 
FROM #temp t
WHERE NOT EXISTS (SELECT 1
                  FROM Member m JOIN
                       Person p
                       ON p.PersonID = m.PersonID
                  WHERE MemberID <>  307 and individual_id = t. individual_id
                 );

但是,我不知道在这种情况下这是否会有更好的表现。

答案 1 :(得分:1)

此行可能会杀死第一个查询

WHERE CAST(individual_id as varchar) NOT IN 

我的猜测是强制进行表扫描而不是使用任何索引。