【问题标题】:Not Exists vs Not In: efficiency不存在 vs 不存在:效率
【发布时间】:2015-09-28 13:16:09
【问题描述】:

我一直认为不存在是要走的路,而不是使用 not in 条件。但是,我对我一直在使用的查询进行了比较,我注意到 Not In 条件的执行实际上似乎更快。任何关于为什么会出现这种情况的见解,或者如果我在此之前只是做了一个可怕的假设,将不胜感激!

问题 1:

SELECT DISTINCT 
a.SFAccountID, a.SLXID, a.Name FROM [dbo].[Salesforce_Accounts] a WITH(NOLOCK)
JOIN  _SLX_AccountChannel b WITH(NOLOCK)
ON a.SLXID = b.ACCOUNTID
JOIN [dbo].[Salesforce_Contacts] c WITH(NOLOCK)
ON a.SFAccountID = c.SFAccountID
WHERE b.STATUS IN ('Active','Customer', 'Current')
AND c.Primary__C = 0
AND NOT EXISTS
(
SELECT 1 FROM [dbo].[Salesforce_Contacts] c2 WITH(NOLOCK)
WHERE a.SFAccountID = c2.SFAccountID
AND c2.Primary__c = 1
);

问题 2:

SELECT   
DISTINCT
a.SFAccountID FROM [dbo].[Salesforce_Accounts] a WITH(NOLOCK)
JOIN  _SLX_AccountChannel b WITH(NOLOCK)
ON a.SLXID = b.ACCOUNTID
JOIN [dbo].[Salesforce_Contacts] c WITH(NOLOCK) 
ON a.SFAccountID = c.SFAccountID
WHERE b.STATUS IN ('Active','Customer', 'Current')
AND c.Primary__C = 0
AND a.SFAccountID NOT IN (SELECT SFAccountID FROM [dbo].[Salesforce_Contacts] WHERE Primary__c = 1 AND SFAccountID IS NOT NULL);

查询 1 的实际执行计划:

查询2的实际执行计划:

时间/IO 统计:

查询 #1(使用不存在):

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 532 ms, elapsed time = 533 ms.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Salesforce_Contacts'. Scan count 2, logical reads 3078, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'INFORMATION'. Scan count 1, logical reads 691, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'ACCOUNT'. Scan count 4, logical reads 567, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Salesforce_Accounts'. Scan count 1, logical reads 680, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 250 ms,  elapsed time = 271 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

查询 #2(使用 Not In):

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 500 ms, elapsed time = 500 ms.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Salesforce_Contacts'. Scan count 2, logical reads 3079, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'INFORMATION'. Scan count 1, logical reads 691, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'ACCOUNT'. Scan count 4, logical reads 567, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Salesforce_Accounts'. Scan count 1, logical reads 680, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 157 ms,  elapsed time = 166 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.

【问题讨论】:

  • (1) 实际计划在我看来几乎相同。 (2) 您需要衡量查询的实际性能,而不是计划的估计性能。
  • 我在大型数据库方面的经验让我更喜欢IN 而不是EXISTS。我也不再专门使用CTE,而是更频繁地使用临时表
  • 哦,对不起,那是实际的执行计划。如果放大,则平面图看起来相同,但百分比略有不同。
  • 不要看估计值(是的,即使在实际计划中,这些百分比也是估计值),而是测量实际性能。读取次数、持续时间等。

标签: sql sql-server tsql exists sql-execution-plan


【解决方案1】:

我认为缺少索引会导致 EXISTS()IN 操作的差异。

虽然问题不要求更好的查询,但对我来说我会尽量避免这样的 Distinct

SELECT
    a.SFAccountID, a.SLXID, a.Name 
FROM 
    [dbo].[Salesforce_Accounts] a WITH(NOLOCK)
    CROSS APPLY 
    (
        SELECT SFAccountID 
        FROM [dbo].[Salesforce_Contacts] WITH(NOLOCK) 
        WHERE SFAccountID  = a.SFAccountID 
        GROUP BY SFAccountID
        HAVING MAX(Primary__C + 0) = 0 -- Assume Primary__C is a bit value
    ) b
WHERE
    -- Actually it is the filtering condition for account channel
    EXISTS
    (
        SELECT * FROM _SLX_AccountChannel WITH(NOLOCK) 
        WHERE ACCOUNTID = a.SLXID AND STATUS IN ('Active','Customer', 'Current')
    )

【讨论】:

    【解决方案2】:

    问题是:“为什么NOT IN 似乎比NOT EXISTS 快”。

    我的回答是:它只是看起来更快,但其实是一样的。 (在这种情况下)

    您是否确实测量了两个查询的时间并确认存在差异?

    或者您只是查看了执行计划?

    据我了解,您在屏幕截图中看到的查询成本(53% 对 47%)是:

    • 估计查询成本,即使计划是实际的;
    • 查询成本,而不是时间,是由 CPU 和 IO“成本”组合而成。

    在这种特殊情况下,查询优化器似乎为两个查询生成了几乎相同的计划。计划中某些运算符的估计行数很可能(略有)不同,但实际性能是相同的,因为计划形状是相同的。如果估计的行数不同,则会导致您看到的估计查询成本不同。

    要查看计划中的差异(如果有),我会使用SQL Sentry Plan Explorer 之类的工具。它显示了更多详细信息,您可以更轻松地比较查询的各个方面。


    将查询重写为更快是另一个问题,我不在这里尝试回答。

    【讨论】:

      【解决方案3】:

      您无需多次点击/加入Salesforce_Contacts 即可。这更紧凑、更快:

      SELECT a.SFAccountID, a.SLXID, a.Name
      FROM [dbo].[Salesforce_Accounts] a WITH(NOLOCK)
      JOIN  _SLX_AccountChannel b WITH(NOLOCK)
          ON a.SLXID = b.ACCOUNTID
      JOIN [dbo].[Salesforce_Contacts] c WITH(NOLOCK)
          ON a.SFAccountID = c.SFAccountID
      WHERE b.STATUS IN ('Active','Customer', 'Current')
      GROUP BY a.SFAccountID, a.SLXID, a.Name
      HAVING MAX(c.Primary__C) = 0
      

      INEXISTS 之间的差异可以忽略不计。

      【讨论】:

        【解决方案4】:

        这是假设您正在尝试查找没有主要联系人且只能有一个主要联系人的帐户

        SELECT  a.SFAccountID, a.SLXID, a.Name 
        FROM    [dbo].[Salesforce_Accounts] a
                LEFT JOIN [dbo].[Salesforce_Contacts] c ON a.SFAccountID = c.SFAccountID AND c.Primary__C = 1
        WHERE
                EXISTS (SELECT  * 
                        FROM SLX_AccountChannel b 
                        WHERE b.ACCOUNTID = a.SLXID 
                            AND b.STATUS IN ( 'Active', 'Customer', 'Current' ))
                AND c.SFContactID IS NULL
        

        如果您想要有联系人但没有主要联系人的帐户,您可以使用

        SELECT 
            a.SFAccountID ,
            a.SLXID ,
            a.Name
        FROM 
            [dbo].[Salesforce_Accounts] a
        WHERE
            a.SFAccountID IN (SELECT SFAccountID 
                            FROM [Salesforce_Contacts] 
                            GROUP BY SFAccountID 
                            HAVING SUM(CAST(Primary__c AS INT) = 0))
        
            AND a.SLXID IN (SELECT ACCOUNTID 
                            FROM _SLX_AccountChannel 
                            WHERE [STATUS] IN ( 'Active', 'Customer', 'Current' ))
        

        【讨论】:

        • 嘿垃圾话。 -1 你完全想念 [dbo].[Salesforce_Contacts].Primary__C = 0
        • 你怎么知道他只想要有 1 个非主要联系人的帐户?
        • 有“AND c.Primary__C = 0”。我不知道他想要什么,但我知道查询的作用。
        • @Blam 等你长大一点,我们可以谈谈为什么将 3 个表连接在一起然后使用 distinct,因为你真的只需要 1 个表中的信息是一种不好的做法
        • 版主请不要反对@Blam 我靠重写像他这样的查询来解决性能问题过得很好.
        【解决方案5】:

        据我了解,一个 not in 的工作方式与两个嵌套 for 指令的工作方式相同。

        所以,假设你有两个表:table(1000 条记录)和 tabla(2000 条记录),

        select * from table where table.field not in (select field from tabla)
        

        就像在做

        for (int i = 0;  i < 1000; i++) {
           for (int j = 0;  j < 2000; j++) {
           }
        }
        

        即 1000*2000 = 200 万次操作。

        与 tabla.field 的左连接是空技巧,据我所知,再次只进行 2000 次操作

        使用左连接。

        【讨论】:

        • 在一个没有查询优化器的世界里,一切都在内存中,当然。在现实世界中,没有那么多......
        • 在本地数据库中进行实验。慷慨地填写它,我会说每张桌子有 100.000 条记录。测量两个选项的时间(不加入和左加入),你得到什么时间?
        【解决方案6】:

        试试

        SELECT DISTINCT a.SFAccountID, a.SLXID, a.Name 
          FROM [dbo].[Salesforce_Accounts] a WITH(NOLOCK)
          JOIN _SLX_AccountChannel b WITH(NOLOCK)
            ON a.SLXID = b.ACCOUNTID
           AND b.STATUS IN ('Active','Customer', 'Current')
          JOIN [dbo].[Salesforce_Contacts] c WITH(NOLOCK)
            ON a.SFAccountID = c.SFAccountID 
           AND c.Primary__C = 0
          LEFT JOIN [dbo].[Salesforce_Contacts] c2 WITH(NOLOCK) 
            on c2.SFAccountID = a.SFAccountID
           AND c2.Primary__c = 1
         WHERE c2.SFAccountID is null 
        

        【讨论】:

        • 这种类型的 joinwhere 在大多数情况下并没有太大的区别
        • @JamieD77 如果它确实有所作为,那就更好了。我这样做是为了生活。
        • 这个更快。我可以简要解释一下为什么这会更快吗?谢谢!
        • 以它为生,你会认为你会提到将连接分离成 subqueryIN 语句而不是 DISTINCT,因为所有字段都来自[Salesforce_Accounts]
        • @JamieD77 也许当你以它为生时,你会知道加入会导致什么重复,它在 Salesforce_Accounts 中甚至可能不是唯一的。如果你有更好的发布它。您确实收到了 OP 的评论,它更快?
        猜你喜欢
        • 2013-09-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-07
        • 1970-01-01
        • 2014-06-10
        相关资源
        最近更新 更多