【问题标题】:SQL performance on LEFT OUTER JOIN vs NOT EXISTS左外连接与不存在的 SQL 性能
【发布时间】:2011-07-21 14:43:02
【问题描述】:

如果我想在表 A 但不在表 B 中找到一组条目,我可以使用 LEFT OUTER JOIN 或 NOT EXISTS。我听说 SQL Server 面向 ANSI,在某些情况下,LEFT OUTER JOIN 比 NOT EXISTS 高效得多。在这种情况下,ANSI JOIN 会表现更好吗?在 SQL Server 上,连接运算符是否比 NOT EXISTS 更有效?

【问题讨论】:

  • 您的问题过于笼统,无法回答,请提供具体示例。

标签: sql sql-server


【解决方案1】:

Joe 的链接是一个很好的起点。 Quassnoi covers this too.

一般而言,如果您的字段被正确索引,或者如果您希望过滤掉更多记录(即在子查询中有很多行 EXISTNOT EXISTS 会表现更好。

EXISTSNOT EXISTS 都短路了 - 只要一条记录符合条件,它就会被包含或过滤掉,优化器就会转到下一条记录。

LEFT JOIN 将加入ALL RECORDS,无论它们是否匹配,然后过滤掉所有不匹配的记录。如果您的表很大和/或您有多个 JOIN 条件,这可能会非常耗费资源。

我通常会尽可能使用NOT EXISTSEXISTS。对于 SQL Server,INNOT IN 在语义上是等效的,并且可能更容易编写。 这些是您可以在 SQL Server 中找到的唯一可以保证短路的运算符。

【讨论】:

  • EXCEPT 和 INTERSECT 也与 (NOT) EXISTS 相同。当然,NOT IN 以 NULL 失败...
  • @gbn - 没有意识到这一点。我通常只在涉及很多领域并且在EXISTS 子句中更难破译时使用那些
  • 避免NOT IN。至少在编写代码或使用 ORM 时,“In 列表”中的元素数量是有限的。另请参阅:sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join.
  • 我只有一个问题。如果我有 10000 条匹配记录并且在子查询中使用不存在。这不意味着我的子查询将运行 10000 次(即每条记录)并降低性能,在另一个左连接将一次带来所有记录,然后过滤 10000 个匹配记录并提供更好的性能
  • 这个答案不再正确。在 SQL Server 2017(及更高版本(甚至更早))中,两者都可能发生短路。在大多数情况下,查询优化器正确地将left join where is null 转换为anti semi join,就像它对not exists 所做的那样。有时每个运营商会产生不同的计划。有时一个比另一个快。 视情况而定。
【解决方案2】:

就我个人而言,我认为这是一个很老的“它取决于”。我见过每种方法都优于其他方法的实例。

最好的办法是测试两者,看看哪个表现更好。如果在这种情况下表格总是很小并且性能并不那么重要,那么我会选择你最清楚的那个(对于大多数人来说通常是NOT EXISTS)并继续前进。

【讨论】:

  • 这真的取决于,我刚刚重写了两个使用不存在的查询,并用left outer join with null check 替换了not exists,是的,它确实表现得更好。但总是选择 Not Exists,大多数时候它会表现得更好,使用Not Exists时意图更清晰。
【解决方案3】:

blog entry 给出了各种方式的示例(NOT INOUTER APPLYLEFT OUTER JOINEXCEPTNOT EXISTS )达到相同的结果,并证明不存在(左反半连接)是冷缓存和暖缓存场景中的最佳选择。

【讨论】:

    【解决方案4】:

    我一直想知道在 OP 描述的这些情况下,我们如何使用要删除的表上的索引。

    假设我们有:

     table EMPLOYEE (emp_id int, name varchar) 
    and
     table EMPLOYEE_LOCATION (emp_id int, loc_id int)
    

    在我的真实示例中,我的表要宽得多,包含超过 100 万行,出于示例目的,我已经简化了架构。

    如果我想从 EMPLOYEE_LOCATION 中删除在 EMPLOYEE 中没有相应 emp_id 的行,我显然可以使用左外部技术或 NOT IN 但我想知道...

    如果两个表的索引都具有 emp_id 的前导列,那么是否值得尝试使用它们?

    也许我可以从 EMPLOYEE 中提取 emp_id,从 EMPLOYEE_LOCATION 中提取 emp_id 到一个临时表中,然后从我要删除的临时表中获取 emp_id。

    然后我可以循环这些 emp_id 并实际使用索引,如下所示:

    loop for each emp_id X to delete -- (this would be a cursor)
     DELETE EMPLOYEE_LOCATION WHERE emp_id = X
    

    我知道光标有开销,但在我的实际示例中,我正在处理巨大的表,所以我认为显式使用索引是可取的。

    【讨论】:

      【解决方案5】:

      Answer 上 dba.stackexchange

      我注意到NOT EXISTS 优于LEFT JOIN ... WHERE IS NULL 的一个例外是使用链接服务器

      通过检查执行计划,NOT EXISTS 运算符似乎以嵌套循环方式执行。因此它是按行执行的(我认为这是有道理的)。

      演示此行为的示例执行计划:

      【讨论】:

        猜你喜欢
        • 2021-09-02
        • 1970-01-01
        • 1970-01-01
        • 2011-10-01
        • 2015-06-13
        • 2016-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多