【问题标题】:How to improve performance of this SQL Server query?如何提高此 SQL Server 查询的性能?
【发布时间】:2014-09-21 00:35:11
【问题描述】:

我在网络开发人员面试中被问到这个问题。在我回答面试官说你在第二张桌子之后:(

我有两张桌子employeebademployee

  • employee (empid int pk,名称 varchar(20)`)
  • bademployee (badempid int pk, name varchar(20))

现在,我只想选择优秀的员工。

我的回答是:

SELECT * 
FROM employee 
WHERE empid NOT IN (SELECT badempid from bademployee)

他说这个查询对性能不好。

谁能告诉我如何为相同的结果编写查询,不使用否定词(不是在,!=)。

可以使用LEFT OUTER JOIN 完成吗?

【问题讨论】:

    标签: sql-server


    【解决方案1】:

    这可以使用OUTER JOINNULL 检查或使用NOT EXISTS 重写。我更喜欢NOT EXISTS

    SELECT *
    FROM Employee e
    WHERE NOT EXISTS (
        SELECT 1
        FROM bademployee b
        WHERE e.empid = b.badempid)
    

    这里是OUTER JOIN,但我相信NOT EXISTS 的性能会更好。

    SELECT e.*
    FROM Employee e
        LEFT JOIN bademployee b ON e.empid = b.badempid
    WHERE b.badempid IS NULL
    

    这是一篇关于性能差异的有趣文章:http://sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join

    【讨论】:

    • 我喜欢第二个。但这对于 IT 领域的新人来说是正常的问题吗?
    • @PATILDADA 第二个查询执行最差。 NOT IN 不一定是坏的,除非你的右侧查询中有 NULL 值。不存在是首选。检查他发布的链接,它对这个主题非常有用。
    • @PADILDADA,您发布的链接中的示例与面试问题不同。在 Pinal Day 的示例中,连接的列不是两个表的主键。重要的是,Pinal Dave 说“视情况而定”,这几乎总是这些问题的正确答案。
    【解决方案2】:

    无论其他人怎么说,您都需要检查执行计划并根据该内容得出结论。永远不要只相信声称这个或那个的其他人,研究他的主张并通过有关该主题的文档以及在这种情况下清楚地告诉您发生了什么的执行计划来验证这一点。

    来自 SQL 权威博客的One example 表明,LEFT JOIN 解决方案的性能比 NOT IN 解决方案差得多。这是由于查询计划程序执行的 LEFT ANTI SEMI JOIN 通常比 LEFT JOIN + NULL 检查执行得更好。当行数很少时,可能会有例外。后面作者也和第一段一样告诉你:总是检查执行计划。

    来自 SQL 性能博客的Another blog post 用实际的性能测试结果进一步探讨了这一点。

    TL;DR:就性能而言,NOT EXISTS 和 NOT IN 处于同一级别,但由于 NULL 值存在问题,因此首选 NOT EXISTS。此外,不要只相信任何人声称的内容,研究并验证您的执行计划。

    【讨论】:

      【解决方案3】:

      我认为面试官对绩效差异的看法是错误的。因为连接列在两个表中是唯一的且不为空,所以NOT INNOT EXISTSLEFT JOIN...WHERE IS NULL 查询在语义上是相同的。 SQL 是一种声明性语言,因此 SQL Server 优化器可以提供最佳且相同的计划,而不管现在是否表达了查询。也就是说,它并不总是完美的,因此可能存在差异,尤其是对于更复杂的查询。

      下面是一个演示这一点的脚本。在我的 SQL Server 2014 机器上,我看到前 2 个查询(有序聚集索引扫描和合并连接)的执行计划相同,最后添加了一个过滤器运算符。我希望所有 3 个都具有相同的性能,因此从性能的角度来看这并不重要。我通常会使用NOT EXISTS,因为意图更清晰,并且在 NOT IN 子查询返回 NULL 的情况下避免了陷阱,从而导致由于 UNKNOWN 谓词结果返回零行。

      我不会像这样概括性能比较。如果连接的列允许NULL 或不能保证唯一,则这些查询在语义上是不同的,因此可能会产生不同的执行计划。

      CREATE TABLE dbo.employee (
          empid int CONSTRAINT pk_employee PRIMARY KEY
          , name varchar(20)
          );
      
      CREATE TABLE dbo.bademployee (
            badempid int CONSTRAINT pk_bademployee PRIMARY KEY
          , name varchar(20)
          );
      
      WITH 
          t4 AS (SELECT n FROM (VALUES(0),(0),(0),(0)) t(n))
          ,t256 AS (SELECT 0 AS n FROM t4 AS a CROSS JOIN t4 AS b CROSS JOIN t4 AS c CROSS JOIN t4 AS d)
          ,t16M AS (SELECT ROW_NUMBER() OVER (ORDER BY (a.n)) AS num FROM t256 AS a CROSS JOIN t256 AS b CROSS JOIN t256 AS c)
      INSERT INTO dbo.employee(empid, name)
      SELECT num, 'Employee name ' + CAST(num AS varchar(10))
      FROM t16M
      WHERE num <= 10000;
      
      INSERT INTO dbo.bademployee(badempid, name)
      SELECT TOP 5 PERCENT empid, name
      FROM dbo.employee
      ORDER BY NEWID();
      GO
      UPDATE STATISTICS dbo.employee WITH FULLSCAN;
      UPDATE STATISTICS dbo.bademployee WITH FULLSCAN;
      GO
      
      SELECT * 
      FROM employee 
      WHERE empid NOT IN (SELECT badempid from bademployee);
      
      SELECT *
      FROM Employee e
      WHERE NOT EXISTS (
          SELECT 1
          FROM bademployee b
          WHERE e.empid = b.badempid);
      
      SELECT e.*
      FROM Employee e
          LEFT JOIN bademployee b ON e.empid = b.badempid
      WHERE b.badempid IS NULL;
      GO
      

      【讨论】:

        猜你喜欢
        • 2019-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-09
        • 2014-03-23
        • 2011-03-03
        • 1970-01-01
        相关资源
        最近更新 更多